From 62994720640f1da760fb23dc1380e776a69a525a Mon Sep 17 00:00:00 2001 From: "Robert J. Lang" Date: Thu, 20 Jun 2024 09:31:23 -0700 Subject: [PATCH] Upgrade CiviCRM to version 5.74.4. --- .../Gdpr/CommunicationsPreferences/Utils.php | 6 +- .../Gdpr/Form/CommunicationsPreferences.php | 2 +- .../ContributionPage/TermsAndConditions.php | 16 +- .../Form/ManageEvent/TermsAndConditions.php | 6 +- .../CRM/Gdpr/Hook.php | 2 +- .../CRM/Gdpr/SLA/Entity.php | 12 +- .../CRM/Gdpr/Upgrader.php | 2 +- .../CRM/Gdpr/Upgrader/Base.php | 396 - .../CRM/Gdpr/Utils.php | 1 - .../uk.co.vedaconsulting.gdpr/composer.json | 5 + .../uk.co.vedaconsulting.gdpr/docs/hooks.md | 10 + .../uk.co.vedaconsulting.gdpr/gdpr.civix.php | 304 +- .../uk.co.vedaconsulting.gdpr/gdpr.php | 101 +- .../uk.co.vedaconsulting.gdpr/info.xml | 19 +- .../mixin/menu-xml@1.0.0.mixin.php | 31 + .../mixin/mgd-php@1.0.0.mixin.php | 42 + .../mixin/polyfill.php | 101 + .../mixin/smarty-v2@1.0.1.mixin.php | 51 + .../package-lock.json | 3813 ++----- .../uk.co.vedaconsulting.gdpr/package.json | 8 +- .../Gdpr/Form/CommunicationsPreferences.tpl | 8 +- .../ContributionPage/TermsAndConditions.tpl | 2 +- .../Form/ManageEvent/TermsAndConditions.tpl | 2 +- .../templates/CRM/Gdpr/Form/Settings.tpl | 5 +- .../templates/CRM/Gdpr/Form/Task/Contact.tpl | 8 +- .../CRM/Gdpr/Form/UpdatePreference.tpl | 4 +- www/modules/civicrm/CRM/ACL/DAO/ACL.php | 2 +- www/modules/civicrm/CRM/ACL/DAO/ACLCache.php | 2 +- .../civicrm/CRM/ACL/DAO/ACLEntityRole.php | 2 +- .../CRM/ACL/Form/WordPress/Permissions.php | 4 +- .../civicrm/CRM/Activity/ActionMapping.php | 2 +- .../civicrm/CRM/Activity/BAO/Activity.php | 80 +- .../civicrm/CRM/Activity/DAO/Activity.php | 2 +- .../CRM/Activity/DAO/ActivityContact.php | 2 +- .../civicrm/CRM/Activity/Form/Activity.php | 33 +- .../civicrm/CRM/Activity/Form/Task/PDF.php | 12 +- .../civicrm/CRM/Activity/Form/Task/SMS.php | 95 +- .../CRM/Activity/Import/Controller.php | 41 - .../civicrm/CRM/Activity/Selector/Search.php | 2 +- www/modules/civicrm/CRM/Admin/Form.php | 2 +- .../civicrm/CRM/Admin/Form/CMSUser.php | 11 +- .../CRM/Admin/Form/MessageTemplates.php | 18 +- .../civicrm/CRM/Admin/Form/Options.php | 50 +- .../CRM/Admin/Form/ScheduleReminders.php | 15 +- .../civicrm/CRM/Admin/Form/Setting.php | 8 + .../civicrm/CRM/Admin/Form/Setting/Case.php | 9 + .../CRM/Admin/Form/Setting/Component.php | 9 + .../civicrm/CRM/Admin/Form/Setting/Date.php | 11 +- .../CRM/Admin/Form/Setting/Debugging.php | 9 + .../CRM/Admin/Form/Setting/Localization.php | 9 + .../civicrm/CRM/Admin/Form/Setting/Mail.php | 9 + .../CRM/Admin/Form/Setting/Mapping.php | 9 + .../CRM/Admin/Form/Setting/Miscellaneous.php | 9 + .../civicrm/CRM/Admin/Form/Setting/Path.php | 9 + .../civicrm/CRM/Admin/Form/Setting/Smtp.php | 43 +- .../civicrm/CRM/Admin/Form/Setting/UF.php | 2 - .../civicrm/CRM/Admin/Form/Setting/Url.php | 21 +- www/modules/civicrm/CRM/Admin/Page/AJAX.php | 18 +- .../civicrm/CRM/Admin/Page/Extensions.php | 3 +- .../CRM/Admin/Page/MessageTemplates.php | 5 + .../civicrm/CRM/Admin/Page/Options.php | 2 + www/modules/civicrm/CRM/Badge/BAO/Layout.php | 1 + www/modules/civicrm/CRM/Batch/DAO/Batch.php | 2 +- .../civicrm/CRM/Batch/DAO/EntityBatch.php | 2 +- www/modules/civicrm/CRM/Batch/Form/Entry.php | 68 +- .../civicrm/CRM/Campaign/BAO/Survey.php | 7 +- .../civicrm/CRM/Campaign/DAO/Campaign.php | 2 +- .../CRM/Campaign/DAO/CampaignGroup.php | 2 +- .../civicrm/CRM/Campaign/DAO/Survey.php | 2 +- .../civicrm/CRM/Campaign/Form/Campaign.php | 163 +- .../CRM/Campaign/Form/CampaignFormTrait.php | 51 + .../civicrm/CRM/Campaign/Form/Petition.php | 34 +- .../civicrm/CRM/Campaign/Form/Survey.php | 194 +- .../civicrm/CRM/Campaign/Form/Survey/Main.php | 15 +- .../CRM/Campaign/Form/Survey/Questions.php | 24 +- .../CRM/Campaign/Form/Survey/Results.php | 2 +- .../CRM/Campaign/Form/Survey/TabHeader.php | 19 +- www/modules/civicrm/CRM/Campaign/Info.php | 40 +- .../civicrm/CRM/Campaign/Page/AJAX.php | 2 +- .../CRM/Campaign/Page/Petition/ThankYou.php | 6 +- .../civicrm/CRM/Campaign/Selector/Search.php | 29 +- www/modules/civicrm/CRM/Case/Audit/Audit.php | 2 +- www/modules/civicrm/CRM/Case/BAO/Case.php | 34 +- www/modules/civicrm/CRM/Case/DAO/Case.php | 2 +- .../civicrm/CRM/Case/DAO/CaseActivity.php | 2 +- .../civicrm/CRM/Case/DAO/CaseContact.php | 2 +- www/modules/civicrm/CRM/Case/DAO/CaseType.php | 2 +- .../civicrm/CRM/Case/Form/Activity.php | 92 +- .../Form/Activity/ChangeCaseStartDate.php | 6 +- .../Case/Form/Activity/ChangeCaseStatus.php | 23 +- .../CRM/Case/Form/Activity/OpenCase.php | 10 +- www/modules/civicrm/CRM/Case/Form/Case.php | 135 +- .../civicrm/CRM/Case/Form/CaseFormTrait.php | 51 + .../civicrm/CRM/Case/Form/CaseView.php | 2 + .../civicrm/CRM/Case/Form/CustomData.php | 15 +- www/modules/civicrm/CRM/Case/Form/Report.php | 4 +- www/modules/civicrm/CRM/Case/Info.php | 34 +- .../civicrm/CRM/Case/ManagedEntities.php | 2 +- .../civicrm/CRM/Case/Selector/Search.php | 2 +- .../Case/WorkflowMessage/ActivityExamples.php | 69 + .../CRM/Case/WorkflowMessage/CaseActivity.php | 90 + .../civicrm/CRM/Case/XMLProcessor/Report.php | 8 +- .../civicrm/CRM/Contact/AccessTrait.php | 21 +- .../civicrm/CRM/Contact/ActionMapping.php | 13 +- .../civicrm/CRM/Contact/BAO/Contact.php | 51 +- .../civicrm/CRM/Contact/BAO/ContactType.php | 229 +- www/modules/civicrm/CRM/Contact/BAO/Group.php | 50 +- .../civicrm/CRM/Contact/BAO/Individual.php | 97 +- www/modules/civicrm/CRM/Contact/BAO/Query.php | 15 +- .../civicrm/CRM/Contact/BAO/Relationship.php | 21 +- .../civicrm/CRM/Contact/BAO/SavedSearch.php | 36 + .../CRM/Contact/DAO/ACLContactCache.php | 2 +- .../civicrm/CRM/Contact/DAO/Contact.php | 2 +- .../civicrm/CRM/Contact/DAO/ContactType.php | 2 +- .../CRM/Contact/DAO/DashboardContact.php | 2 +- www/modules/civicrm/CRM/Contact/DAO/Group.php | 2 +- .../civicrm/CRM/Contact/DAO/GroupContact.php | 2 +- .../CRM/Contact/DAO/GroupContactCache.php | 2 +- .../civicrm/CRM/Contact/DAO/GroupNesting.php | 2 +- .../CRM/Contact/DAO/GroupOrganization.php | 2 +- .../civicrm/CRM/Contact/DAO/Relationship.php | 2 +- .../CRM/Contact/DAO/RelationshipCache.php | 2 +- .../CRM/Contact/DAO/RelationshipType.php | 2 +- .../civicrm/CRM/Contact/DAO/SavedSearch.php | 2 +- .../CRM/Contact/DAO/SubscriptionHistory.php | 2 +- .../civicrm/CRM/Contact/Form/Contact.php | 331 +- .../civicrm/CRM/Contact/Form/CustomData.php | 135 +- .../civicrm/CRM/Contact/Form/Domain.php | 9 +- .../civicrm/CRM/Contact/Form/Edit/Address.php | 106 +- .../Form/Edit/CommunicationPreferences.php | 2 +- .../CRM/Contact/Form/Edit/CustomData.php | 19 +- .../civicrm/CRM/Contact/Form/Inline.php | 40 +- .../Form/Inline/CommunicationPreferences.php | 2 +- .../CRM/Contact/Form/Inline/CustomData.php | 82 +- .../civicrm/CRM/Contact/Form/Merge.php | 15 +- .../civicrm/CRM/Contact/Form/Relationship.php | 32 +- .../civicrm/CRM/Contact/Form/Search.php | 6 +- .../CRM/Contact/Form/Search/Builder.php | 6 +- .../CRM/Contact/Form/Task/AddToGroup.php | 23 +- .../Contact/Form/Task/AddToParentClass.php | 2 +- .../civicrm/CRM/Contact/Form/Task/Delete.php | 17 +- .../CRM/Contact/Form/Task/EmailCommon.php | 7 +- .../civicrm/CRM/Contact/Form/Task/Map.php | 6 +- .../civicrm/CRM/Contact/Form/Task/PDF.php | 2 +- .../civicrm/CRM/Contact/Form/Task/SMS.php | 34 +- .../CRM/Contact/Form/Task/SMSCommon.php | 22 +- .../CRM/Contact/Form/Task/SMSTrait.php | 421 + .../CRM/Contact/Form/Task/SaveSearch.php | 19 +- .../civicrm/CRM/Contact/Import/Controller.php | 41 - .../CRM/Contact/Import/Form/MapField.php | 2 +- .../CRM/Contact/Import/Parser/Contact.php | 2 +- www/modules/civicrm/CRM/Contact/Page/AJAX.php | 74 - .../civicrm/CRM/Contact/Page/DashBoard.php | 2 +- .../CRM/Contact/Page/DedupeException.php | 2 +- .../CRM/Contact/Page/Inline/CustomData.php | 2 +- .../Contact/Page/View/ContactSmartGroup.php | 4 +- .../CRM/Contact/Page/View/CustomData.php | 16 +- .../CRM/Contact/Page/View/GroupContact.php | 6 +- .../civicrm/CRM/Contact/Page/View/Log.php | 2 +- .../civicrm/CRM/Contact/Page/View/Summary.php | 20 +- .../CRM/Contact/Page/View/UserDashBoard.php | 2 +- .../Page/View/UserDashBoard/GroupContact.php | 6 +- www/modules/civicrm/CRM/Contact/Selector.php | 4 +- www/modules/civicrm/CRM/Contact/Task.php | 2 +- www/modules/civicrm/CRM/Contact/Tokens.php | 57 +- .../civicrm/CRM/Contribute/ActionMapping.php | 2 +- .../CRM/Contribute/ActionMapping/ByPage.php | 4 +- .../CRM/Contribute/ActionMapping/ByType.php | 4 +- .../CRM/Contribute/BAO/Contribution.php | 184 +- .../CRM/Contribute/BAO/ContributionPage.php | 20 +- .../CRM/Contribute/BAO/ContributionRecur.php | 80 +- .../CRM/Contribute/BAO/ContributionSoft.php | 137 +- .../civicrm/CRM/Contribute/BAO/Premium.php | 29 +- .../CRM/Contribute/DAO/Contribution.php | 2 +- .../CRM/Contribute/DAO/ContributionPage.php | 2 +- .../Contribute/DAO/ContributionProduct.php | 2 +- .../CRM/Contribute/DAO/ContributionRecur.php | 2 +- .../CRM/Contribute/DAO/ContributionSoft.php | 2 +- .../civicrm/CRM/Contribute/DAO/Premium.php | 2 +- .../CRM/Contribute/DAO/PremiumsProduct.php | 2 +- .../civicrm/CRM/Contribute/DAO/Product.php | 9 +- .../civicrm/CRM/Contribute/DAO/Widget.php | 2 +- .../Contribute/Form/AbstractEditPayment.php | 39 +- .../CRM/Contribute/Form/AdditionalInfo.php | 16 +- .../CRM/Contribute/Form/AdditionalPayment.php | 67 +- .../Contribute/Form/CancelSubscription.php | 28 +- .../Contribute/Form/ContributeFormTrait.php | 4 +- .../CRM/Contribute/Form/Contribution.php | 140 +- .../Contribute/Form/Contribution/Confirm.php | 29 +- .../CRM/Contribute/Form/Contribution/Main.php | 70 +- .../Contribute/Form/Contribution/ThankYou.php | 12 +- .../CRM/Contribute/Form/ContributionBase.php | 34 +- .../CRM/Contribute/Form/ContributionPage.php | 2 +- .../Form/ContributionPage/TabHeader.php | 2 +- .../Form/ContributionPage/Widget.php | 4 +- .../CRM/Contribute/Form/ContributionRecur.php | 71 +- .../CRM/Contribute/Form/ContributionView.php | 10 +- .../CRM/Contribute/Form/Task/Invoice.php | 36 +- .../civicrm/CRM/Contribute/Form/Task/PDF.php | 26 +- .../CRM/Contribute/Form/Task/Status.php | 2 +- .../CRM/Contribute/Form/UpdateBilling.php | 16 +- .../Contribute/Form/UpdateSubscription.php | 163 +- .../CRM/Contribute/Import/Controller.php | 41 - .../Contribute/Import/Parser/Contribution.php | 32 +- www/modules/civicrm/CRM/Contribute/Info.php | 36 +- .../CRM/Contribute/Page/ContributionPage.php | 2 +- .../civicrm/CRM/Contribute/Page/Tab.php | 15 +- .../CRM/Contribute/Page/UserDashboard.php | 2 +- .../CRM/Contribute/Selector/Search.php | 6 +- .../RecurringEdit/AlexCancelled.php | 2 +- www/modules/civicrm/CRM/Core/Action.php | 4 + .../civicrm/CRM/Core/BAO/ActionSchedule.php | 4 +- www/modules/civicrm/CRM/Core/BAO/Address.php | 5 + www/modules/civicrm/CRM/Core/BAO/Block.php | 4 +- www/modules/civicrm/CRM/Core/BAO/Cache.php | 6 + www/modules/civicrm/CRM/Core/BAO/Country.php | 2 +- .../civicrm/CRM/Core/BAO/CustomField.php | 550 +- .../civicrm/CRM/Core/BAO/CustomGroup.php | 897 +- .../civicrm/CRM/Core/BAO/CustomValue.php | 42 +- .../civicrm/CRM/Core/BAO/CustomValueTable.php | 79 +- .../civicrm/CRM/Core/BAO/EntityTag.php | 6 +- www/modules/civicrm/CRM/Core/BAO/Job.php | 8 + .../civicrm/CRM/Core/BAO/LabelFormat.php | 2 +- www/modules/civicrm/CRM/Core/BAO/Log.php | 32 +- .../civicrm/CRM/Core/BAO/MailSettings.php | 54 +- www/modules/civicrm/CRM/Core/BAO/Mapping.php | 7 +- .../civicrm/CRM/Core/BAO/MessageTemplate.php | 2 +- .../civicrm/CRM/Core/BAO/Navigation.php | 12 +- .../civicrm/CRM/Core/BAO/OptionValue.php | 6 + .../civicrm/CRM/Core/BAO/PaperSize.php | 2 +- .../civicrm/CRM/Core/BAO/PdfFormat.php | 2 +- www/modules/civicrm/CRM/Core/BAO/Phone.php | 33 + .../civicrm/CRM/Core/BAO/PrevNextCache.php | 78 +- www/modules/civicrm/CRM/Core/BAO/Query.php | 6 + .../civicrm/CRM/Core/BAO/RecurringEntity.php | 2 +- .../civicrm/CRM/Core/BAO/SchemaHandler.php | 8 +- www/modules/civicrm/CRM/Core/BAO/Tag.php | 3 +- www/modules/civicrm/CRM/Core/BAO/UFField.php | 38 +- www/modules/civicrm/CRM/Core/BAO/UFGroup.php | 107 +- www/modules/civicrm/CRM/Core/BAO/UFMatch.php | 4 +- www/modules/civicrm/CRM/Core/BAO/UserJob.php | 2 +- www/modules/civicrm/CRM/Core/BAO/Website.php | 2 +- .../civicrm/CRM/Core/BAO/WordReplacement.php | 7 +- www/modules/civicrm/CRM/Core/Block.php | 12 +- www/modules/civicrm/CRM/Core/ClassLoader.php | 4 + .../civicrm/CRM/Core/CodeGen/GenerateData.php | 7 + www/modules/civicrm/CRM/Core/CodeGen/Main.php | 2 +- .../civicrm/CRM/Core/CodeGen/PhpSchema.php | 149 + .../civicrm/CRM/Core/CodeGen/Schema.php | 11 +- .../CRM/Core/CodeGen/Specification.php | 241 +- .../Core/CodeGen/Util/MessageTemplates.php | 13 +- .../civicrm/CRM/Core/CodeGen/Util/Smarty.php | 42 +- .../CRM/Core/CodeGen/Util/Template.php | 5 + .../civicrm/CRM/Core/Component/Info.php | 14 +- www/modules/civicrm/CRM/Core/Config.php | 19 +- .../civicrm/CRM/Core/Config/MagicMerge.php | 13 +- www/modules/civicrm/CRM/Core/Controller.php | 5 +- www/modules/civicrm/CRM/Core/DAO.php | 347 +- .../civicrm/CRM/Core/DAO/ActionLog.php | 2 +- .../civicrm/CRM/Core/DAO/ActionSchedule.php | 4 +- www/modules/civicrm/CRM/Core/DAO/Address.php | 2 +- .../civicrm/CRM/Core/DAO/AddressFormat.php | 2 +- .../CRM/Core/DAO/AllCoreTables.data.php | 6 +- .../civicrm/CRM/Core/DAO/AllCoreTables.php | 291 +- www/modules/civicrm/CRM/Core/DAO/Base.php | 267 + www/modules/civicrm/CRM/Core/DAO/Cache.php | 2 +- .../civicrm/CRM/Core/DAO/Component.php | 2 +- www/modules/civicrm/CRM/Core/DAO/Country.php | 2 +- www/modules/civicrm/CRM/Core/DAO/County.php | 2 +- .../civicrm/CRM/Core/DAO/CustomField.php | 39 +- .../civicrm/CRM/Core/DAO/CustomGroup.php | 2 +- .../civicrm/CRM/Core/DAO/Dashboard.php | 2 +- www/modules/civicrm/CRM/Core/DAO/Discount.php | 2 +- www/modules/civicrm/CRM/Core/DAO/Domain.php | 2 +- www/modules/civicrm/CRM/Core/DAO/Email.php | 2 +- .../civicrm/CRM/Core/DAO/EntityFile.php | 2 +- .../civicrm/CRM/Core/DAO/EntityTag.php | 2 +- .../civicrm/CRM/Core/DAO/Extension.php | 2 +- www/modules/civicrm/CRM/Core/DAO/File.php | 2 +- www/modules/civicrm/CRM/Core/DAO/IM.php | 2 +- www/modules/civicrm/CRM/Core/DAO/Job.php | 34 +- www/modules/civicrm/CRM/Core/DAO/JobLog.php | 2 +- www/modules/civicrm/CRM/Core/DAO/LocBlock.php | 2 +- .../civicrm/CRM/Core/DAO/LocationType.php | 2 +- www/modules/civicrm/CRM/Core/DAO/Log.php | 2 +- .../civicrm/CRM/Core/DAO/MailSettings.php | 7 +- www/modules/civicrm/CRM/Core/DAO/Managed.php | 2 +- www/modules/civicrm/CRM/Core/DAO/Mapping.php | 2 +- .../civicrm/CRM/Core/DAO/MappingField.php | 2 +- www/modules/civicrm/CRM/Core/DAO/Menu.php | 2 +- .../civicrm/CRM/Core/DAO/MessageTemplate.php | 2 +- .../civicrm/CRM/Core/DAO/Navigation.php | 2 +- www/modules/civicrm/CRM/Core/DAO/Note.php | 2 +- www/modules/civicrm/CRM/Core/DAO/OpenID.php | 5 +- .../civicrm/CRM/Core/DAO/OptionGroup.php | 2 +- .../civicrm/CRM/Core/DAO/OptionValue.php | 2 +- www/modules/civicrm/CRM/Core/DAO/Phone.php | 2 +- .../civicrm/CRM/Core/DAO/PreferencesDate.php | 2 +- .../civicrm/CRM/Core/DAO/PrevNextCache.php | 2 +- .../civicrm/CRM/Core/DAO/PrintLabel.php | 2 +- .../civicrm/CRM/Core/DAO/RecurringEntity.php | 2 +- www/modules/civicrm/CRM/Core/DAO/Setting.php | 2 +- .../civicrm/CRM/Core/DAO/StateProvince.php | 2 +- .../civicrm/CRM/Core/DAO/StatusPreference.php | 2 +- .../civicrm/CRM/Core/DAO/SystemLog.php | 2 +- www/modules/civicrm/CRM/Core/DAO/Tag.php | 2 +- www/modules/civicrm/CRM/Core/DAO/Timezone.php | 2 +- .../civicrm/CRM/Core/DAO/Translation.php | 2 +- www/modules/civicrm/CRM/Core/DAO/UFField.php | 2 +- www/modules/civicrm/CRM/Core/DAO/UFGroup.php | 69 +- www/modules/civicrm/CRM/Core/DAO/UFJoin.php | 2 +- www/modules/civicrm/CRM/Core/DAO/UFMatch.php | 2 +- www/modules/civicrm/CRM/Core/DAO/UserJob.php | 2 +- www/modules/civicrm/CRM/Core/DAO/Website.php | 2 +- .../civicrm/CRM/Core/DAO/WordReplacement.php | 2 +- .../civicrm/CRM/Core/DAO/Worldregion.php | 2 +- .../civicrm/CRM/Core/DynamicFKAccessTrait.php | 16 +- www/modules/civicrm/CRM/Core/EntityTokens.php | 5 +- www/modules/civicrm/CRM/Core/Error.php | 9 +- www/modules/civicrm/CRM/Core/Form.php | 92 +- .../civicrm/CRM/Core/Form/EntityFormTrait.php | 45 +- .../civicrm/CRM/Core/Form/Renderer.php | 15 +- www/modules/civicrm/CRM/Core/Form/Task.php | 5 +- www/modules/civicrm/CRM/Core/I18n.php | 92 +- .../civicrm/CRM/Core/I18n/SchemaStructure.php | 5 +- .../Core/I18n/SchemaStructure_4_2_alpha1.php | 219 - .../CRM/Core/I18n/SchemaStructure_4_3_1.php | 224 - .../Core/I18n/SchemaStructure_4_5_alpha1.php | 227 - .../Core/I18n/SchemaStructure_4_5_beta2.php | 235 - .../Core/I18n/SchemaStructure_4_7_alpha1.php | 239 - www/modules/civicrm/CRM/Core/IDS.php | 2 +- www/modules/civicrm/CRM/Core/Invoke.php | 19 +- www/modules/civicrm/CRM/Core/JobManager.php | 17 +- www/modules/civicrm/CRM/Core/Joomla.php | 4 +- www/modules/civicrm/CRM/Core/Page.php | 5 +- www/modules/civicrm/CRM/Core/Payment.php | 15 +- www/modules/civicrm/CRM/Core/Payment/Form.php | 4 +- .../civicrm/CRM/Core/Payment/PayPalImpl.php | 2 +- .../CRM/Core/Payment/ProcessorForm.php | 8 +- www/modules/civicrm/CRM/Core/Permission.php | 449 +- .../civicrm/CRM/Core/Permission/Backdrop.php | 2 +- .../civicrm/CRM/Core/Permission/Base.php | 51 +- .../civicrm/CRM/Core/Permission/Drupal.php | 2 +- .../civicrm/CRM/Core/Permission/Drupal8.php | 3 +- .../civicrm/CRM/Core/Permission/List.php | 29 +- .../CRM/Core/Permission/Standalone.php | 19 +- .../civicrm/CRM/Core/Permission/WordPress.php | 2 +- .../civicrm/CRM/Core/PseudoConstant.php | 2 +- .../civicrm/CRM/Core/Reference/Basic.php | 3 + .../civicrm/CRM/Core/Reference/Dynamic.php | 73 +- www/modules/civicrm/CRM/Core/Region.php | 6 +- www/modules/civicrm/CRM/Core/Resources.php | 8 +- www/modules/civicrm/CRM/Core/ScheduledJob.php | 11 + www/modules/civicrm/CRM/Core/SelectValues.php | 41 +- .../civicrm/CRM/Core/Selector/Controller.php | 16 +- www/modules/civicrm/CRM/Core/Session.php | 13 +- .../civicrm/CRM/Core/ShowHideBlocks.php | 4 +- www/modules/civicrm/CRM/Core/Smarty.php | 187 +- .../CRM/Core/Smarty/UserContentPolicy.php | 207 + .../Core/Smarty/plugins/block.crmScope.php | 26 +- .../plugins/block.crmUpgradeSnapshot.php | 4 +- .../Core/Smarty/plugins/block.localize.php | 4 +- .../CRM/Core/Smarty/plugins/block.ts.php | 9 +- .../Core/Smarty/plugins/function.crmAPI.php | 2 + .../Smarty/plugins/function.crmSqlData.php | 8 +- .../CRM/Core/Smarty/plugins/function.help.php | 71 +- .../modifier.crmEscapeSingleQuotes.php | 22 + .../civicrm/CRM/Core/SmartyCompatibility.php | 97 +- .../Core/WorkflowMessage/Profile/Profile.php | 74 + .../CRM/Core/WorkflowMessage/UFNotify.php | 86 + .../civicrm/CRM/Core/xml/Menu/Admin.xml | 6 +- .../civicrm/CRM/Core/xml/Menu/Contact.xml | 10 - .../civicrm/CRM/Core/xml/Menu/Import.xml | 11 +- .../civicrm/CRM/Custom/Form/CustomData.php | 40 +- .../CRM/Custom/Form/CustomDataTrait.php | 150 + www/modules/civicrm/CRM/Custom/Form/Field.php | 9 +- .../civicrm/CRM/Custom/Import/Controller.php | 30 - .../civicrm/CRM/Custom/Import/Parser/Api.php | 145 +- www/modules/civicrm/CRM/Cxn/DAO/Cxn.php | 2 +- .../civicrm/CRM/Dedupe/BAO/DedupeRule.php | 121 +- .../CRM/Dedupe/BAO/DedupeRuleGroup.php | 229 +- .../CRM/Dedupe/DAO/DedupeException.php | 2 +- .../civicrm/CRM/Dedupe/DAO/DedupeRule.php | 2 +- .../CRM/Dedupe/DAO/DedupeRuleGroup.php | 2 +- www/modules/civicrm/CRM/Dedupe/Finder.php | 242 +- .../civicrm/CRM/Dedupe/MergeHandler.php | 4 +- www/modules/civicrm/CRM/Dedupe/Merger.php | 381 +- .../civicrm/CRM/Event/ActionMapping.php | 2 +- www/modules/civicrm/CRM/Event/BAO/Event.php | 45 +- .../civicrm/CRM/Event/BAO/Participant.php | 108 +- .../civicrm/CRM/Event/Cart/DAO/Cart.php | 2 +- .../CRM/Event/Cart/DAO/EventInCart.php | 2 +- www/modules/civicrm/CRM/Event/DAO/Event.php | 7 +- .../civicrm/CRM/Event/DAO/Participant.php | 12 +- .../CRM/Event/DAO/ParticipantPayment.php | 2 +- .../CRM/Event/DAO/ParticipantStatusType.php | 2 +- .../civicrm/CRM/Event/Form/EventFees.php | 18 +- .../civicrm/CRM/Event/Form/EventFormTrait.php | 77 +- .../civicrm/CRM/Event/Form/ManageEvent.php | 223 +- .../CRM/Event/Form/ManageEvent/EventInfo.php | 24 +- .../CRM/Event/Form/ManageEvent/Fee.php | 2 +- .../CRM/Event/Form/ManageEvent/Location.php | 2 +- .../Event/Form/ManageEvent/Registration.php | 17 +- .../CRM/Event/Form/ManageEvent/TabHeader.php | 16 +- .../civicrm/CRM/Event/Form/Participant.php | 299 +- .../Event/Form/ParticipantFeeSelection.php | 404 +- .../CRM/Event/Form/ParticipantView.php | 102 +- .../civicrm/CRM/Event/Form/Registration.php | 524 +- .../Registration/AdditionalParticipant.php | 30 +- .../CRM/Event/Form/Registration/Confirm.php | 48 +- .../CRM/Event/Form/Registration/Register.php | 355 +- .../CRM/Event/Form/Registration/ThankYou.php | 86 +- .../CRM/Event/Form/Task/AddToGroup.php | 2 +- .../civicrm/CRM/Event/Import/Controller.php | 41 - .../CRM/Event/Import/Form/MapField.php | 29 +- .../CRM/Event/Import/Parser/Participant.php | 211 +- www/modules/civicrm/CRM/Event/Info.php | 44 +- .../civicrm/CRM/Event/Page/EventInfo.php | 94 +- .../civicrm/CRM/Event/Page/ManageEvent.php | 2 +- .../ParticipantListing/NameStatusAndDate.php | 8 +- .../Event/Page/ParticipantListing/Simple.php | 8 +- www/modules/civicrm/CRM/Event/Page/Tab.php | 7 - .../civicrm/CRM/Event/Selector/Search.php | 6 +- .../WorkflowMessage/ParticipantConfirm.php | 26 + .../WorkflowMessage/ParticipantExpired.php | 26 + .../civicrm/CRM/Event/xml/Menu/Event.xml | 12 +- www/modules/civicrm/CRM/Export/BAO/Export.php | 2 +- .../CRM/Export/BAO/ExportProcessor.php | 67 +- .../CRM/Export/Controller/Standalone.php | 2 +- .../civicrm/CRM/Export/Form/Select.php | 2 +- www/modules/civicrm/CRM/Extension/Manager.php | 11 + www/modules/civicrm/CRM/Extension/Mapper.php | 2 +- .../CRM/Financial/BAO/FinancialItem.php | 24 +- .../CRM/Financial/BAO/FinancialType.php | 12 - .../civicrm/CRM/Financial/BAO/Order.php | 29 +- .../civicrm/CRM/Financial/BAO/Payment.php | 260 +- .../civicrm/CRM/Financial/DAO/Currency.php | 2 +- .../Financial/DAO/EntityFinancialAccount.php | 2 +- .../CRM/Financial/DAO/EntityFinancialTrxn.php | 2 +- .../CRM/Financial/DAO/FinancialAccount.php | 2 +- .../CRM/Financial/DAO/FinancialItem.php | 2 +- .../CRM/Financial/DAO/FinancialTrxn.php | 2 +- .../CRM/Financial/DAO/FinancialType.php | 2 +- .../CRM/Financial/DAO/PaymentProcessor.php | 2 +- .../Financial/DAO/PaymentProcessorType.php | 2 +- .../CRM/Financial/DAO/PaymentToken.php | 2 +- .../CRM/Financial/Form/FinancialAccount.php | 16 +- .../civicrm/CRM/Financial/Form/Payment.php | 19 +- .../CRM/Financial/Form/PaymentEdit.php | 2 +- .../CRM/Financial/Form/SalesTaxTrait.php | 2 +- www/modules/civicrm/CRM/Friend/DAO/Friend.php | 2 +- www/modules/civicrm/CRM/Friend/Form.php | 6 +- www/modules/civicrm/CRM/Group/Form/Edit.php | 34 +- www/modules/civicrm/CRM/Import/Controller.php | 55 + www/modules/civicrm/CRM/Import/DataSource.php | 31 +- .../civicrm/CRM/Import/Form/MapField.php | 2 +- www/modules/civicrm/CRM/Import/Forms.php | 27 +- .../civicrm/CRM/Import/ImportProcessor.php | 7 +- www/modules/civicrm/CRM/Import/Parser.php | 163 +- .../civicrm/CRM/Import/StateMachine.php | 46 +- www/modules/civicrm/CRM/Logging/Differ.php | 2 +- .../civicrm/CRM/Logging/ReportDetail.php | 62 +- www/modules/civicrm/CRM/Logging/Schema.php | 12 +- .../civicrm/CRM/Mailing/ActionTokens.php | 4 +- .../civicrm/CRM/Mailing/BAO/Mailing.php | 613 +- .../civicrm/CRM/Mailing/BAO/MailingJob.php | 516 +- .../CRM/Mailing/BAO/MailingTrackableURL.php | 2 +- .../civicrm/CRM/Mailing/BAO/SMSJob.php | 128 +- .../civicrm/CRM/Mailing/DAO/BouncePattern.php | 2 +- .../civicrm/CRM/Mailing/DAO/BounceType.php | 2 +- .../civicrm/CRM/Mailing/DAO/Mailing.php | 4 +- .../civicrm/CRM/Mailing/DAO/MailingAB.php | 2 +- .../CRM/Mailing/DAO/MailingComponent.php | 2 +- .../civicrm/CRM/Mailing/DAO/MailingGroup.php | 2 +- .../civicrm/CRM/Mailing/DAO/MailingJob.php | 2 +- .../CRM/Mailing/DAO/MailingRecipients.php | 2 +- .../CRM/Mailing/DAO/MailingTrackableURL.php | 2 +- www/modules/civicrm/CRM/Mailing/DAO/Spool.php | 2 +- .../Mailing/Event/BAO/MailingEventForward.php | 347 - .../Mailing/Event/BAO/MailingEventOpened.php | 17 +- .../Event/BAO/MailingEventResubscribe.php | 9 +- .../Event/BAO/MailingEventUnsubscribe.php | 4 +- .../Mailing/Event/DAO/MailingEventBounce.php | 2 +- .../Mailing/Event/DAO/MailingEventConfirm.php | 2 +- .../Event/DAO/MailingEventDelivered.php | 2 +- .../Mailing/Event/DAO/MailingEventForward.php | 2 +- .../Mailing/Event/DAO/MailingEventOpened.php | 2 +- .../Mailing/Event/DAO/MailingEventQueue.php | 3 +- .../Mailing/Event/DAO/MailingEventReply.php | 2 +- .../Event/DAO/MailingEventSubscribe.php | 2 +- .../DAO/MailingEventTrackableURLOpen.php | 2 +- .../Event/DAO/MailingEventUnsubscribe.php | 2 +- .../civicrm/CRM/Mailing/Form/Approve.php | 2 +- .../civicrm/CRM/Mailing/Form/Component.php | 15 +- www/modules/civicrm/CRM/Mailing/Info.php | 51 +- .../civicrm/CRM/Mailing/Selector/Search.php | 2 +- .../CRM/Mailing/Service/ListUnsubscribe.php | 4 + .../civicrm/CRM/Member/ActionMapping.php | 24 +- .../civicrm/CRM/Member/BAO/Membership.php | 241 +- .../civicrm/CRM/Member/DAO/Membership.php | 2 +- .../CRM/Member/DAO/MembershipBlock.php | 2 +- .../civicrm/CRM/Member/DAO/MembershipLog.php | 2 +- .../CRM/Member/DAO/MembershipPayment.php | 2 +- .../CRM/Member/DAO/MembershipStatus.php | 5 +- .../civicrm/CRM/Member/DAO/MembershipType.php | 13 +- www/modules/civicrm/CRM/Member/Form.php | 76 +- .../civicrm/CRM/Member/Form/Membership.php | 242 +- .../CRM/Member/Form/MembershipRenewal.php | 16 +- .../CRM/Member/Form/MembershipView.php | 2 +- .../civicrm/CRM/Member/Import/Controller.php | 41 - www/modules/civicrm/CRM/Member/Info.php | 34 +- .../Member/Page/RecurringContributions.php | 2 +- www/modules/civicrm/CRM/Member/Page/Tab.php | 16 +- .../civicrm/CRM/Member/Selector/Search.php | 2 +- .../civicrm/CRM/Member/xml/Menu/Member.xml | 5 +- www/modules/civicrm/CRM/PCP/BAO/PCP.php | 24 + www/modules/civicrm/CRM/PCP/DAO/PCP.php | 7 +- www/modules/civicrm/CRM/PCP/DAO/PCPBlock.php | 2 +- www/modules/civicrm/CRM/PCP/Form/Campaign.php | 4 +- www/modules/civicrm/CRM/PCP/Page/PCP.php | 2 +- .../civicrm/CRM/PCP/StateMachine/PCP.php | 14 +- www/modules/civicrm/CRM/Pledge/DAO/Pledge.php | 2 +- .../civicrm/CRM/Pledge/DAO/PledgeBlock.php | 2 +- .../civicrm/CRM/Pledge/DAO/PledgePayment.php | 2 +- .../civicrm/CRM/Pledge/Form/Pledge.php | 73 +- .../CRM/Pledge/Form/PledgeFormTrait.php | 55 + .../civicrm/CRM/Pledge/Form/PledgeView.php | 118 +- www/modules/civicrm/CRM/Pledge/Info.php | 34 +- www/modules/civicrm/CRM/Pledge/Page/Tab.php | 5 - .../civicrm/CRM/Pledge/Selector/Search.php | 2 +- .../civicrm/CRM/Price/BAO/LineItem.php | 61 +- .../civicrm/CRM/Price/BAO/PriceField.php | 14 +- .../civicrm/CRM/Price/BAO/PriceFieldValue.php | 2 +- .../civicrm/CRM/Price/BAO/PriceSet.php | 3 + .../civicrm/CRM/Price/DAO/LineItem.php | 2 +- .../civicrm/CRM/Price/DAO/PriceField.php | 2 +- .../civicrm/CRM/Price/DAO/PriceFieldValue.php | 2 +- .../civicrm/CRM/Price/DAO/PriceSet.php | 2 +- .../civicrm/CRM/Price/DAO/PriceSetEntity.php | 2 +- www/modules/civicrm/CRM/Price/Form/Field.php | 52 +- www/modules/civicrm/CRM/Profile/Form.php | 2 +- www/modules/civicrm/CRM/Profile/Form/Edit.php | 2 +- .../civicrm/CRM/Profile/Page/Dynamic.php | 6 +- .../civicrm/CRM/Profile/Page/Router.php | 2 +- .../civicrm/CRM/Profile/Selector/Listings.php | 2 +- www/modules/civicrm/CRM/Queue/DAO/Queue.php | 2 +- .../civicrm/CRM/Queue/DAO/QueueItem.php | 2 +- www/modules/civicrm/CRM/Queue/Menu.php | 2 - .../civicrm/CRM/Report/DAO/ReportInstance.php | 2 +- www/modules/civicrm/CRM/Report/Form.php | 26 +- .../CRM/Report/Form/Contact/Detail.php | 4 +- .../civicrm/CRM/Report/Form/Event/Income.php | 6 +- .../civicrm/CRM/Report/Form/Instance.php | 2 +- .../CRM/Report/Form/Mailing/Detail.php | 10 + .../CRM/Report/Form/Membership/Summary.php | 4 +- www/modules/civicrm/CRM/Report/Info.php | 50 +- .../civicrm/CRM/Report/Page/InstanceList.php | 5 + .../civicrm/CRM/Report/Utils/Report.php | 5 +- www/modules/civicrm/CRM/SMS/BAO/Provider.php | 179 +- .../civicrm/CRM/SMS/BAO/SmsProvider.php | 179 + www/modules/civicrm/CRM/SMS/DAO/Provider.php | 462 +- .../civicrm/CRM/SMS/DAO/SmsProvider.php | 462 + www/modules/civicrm/CRM/SMS/Form/Group.php | 10 +- www/modules/civicrm/CRM/SMS/Form/Provider.php | 12 +- www/modules/civicrm/CRM/SMS/Form/Schedule.php | 2 +- www/modules/civicrm/CRM/SMS/Page/Provider.php | 4 +- www/modules/civicrm/CRM/SMS/Provider.php | 55 +- www/modules/civicrm/CRM/UF/Form/Field.php | 7 +- www/modules/civicrm/CRM/UF/Form/Group.php | 6 +- .../civicrm/CRM/UF/Page/ProfileEditor.php | 39 +- .../CRM/Upgrade/Incremental/General.php | 15 +- .../Upgrade/Incremental/MessageTemplates.php | 8 + .../Upgrade/Incremental/php/FiveFiftyOne.php | 92 +- .../Incremental/php/FiveSeventyFour.php | 38 + .../Incremental/php/FiveSeventyOne.php | 60 + .../Incremental/php/FiveSeventyThree.php | 34 + .../Incremental/php/FiveSeventyTwo.php | 61 + .../Incremental/sql/5.71.alpha1.mysql.tpl | 22 + .../Incremental/sql/5.71.beta1.mysql.tpl | 2 + .../Incremental/sql/5.72.alpha1.mysql.tpl | 1 + .../Incremental/sql/5.73.alpha1.mysql.tpl | 1 + .../Incremental/sql/5.74.alpha1.mysql.tpl | 3 + .../Incremental/sql/5.74.beta1.mysql.tpl | 3 + www/modules/civicrm/CRM/Utils/Array.php | 4 +- .../civicrm/CRM/Utils/Cache/Tiered.php | 3 + .../civicrm/CRM/Utils/Check/Component/Env.php | 63 +- .../CRM/Utils/Check/Component/Smarty.php | 22 +- .../CRM/Utils/Check/Component/Source.php | 26 +- .../civicrm/CRM/Utils/Check/Message.php | 10 +- www/modules/civicrm/CRM/Utils/Date.php | 12 +- www/modules/civicrm/CRM/Utils/Hook.php | 17 +- www/modules/civicrm/CRM/Utils/ICalendar.php | 10 +- www/modules/civicrm/CRM/Utils/JS.php | 19 +- www/modules/civicrm/CRM/Utils/Mail.php | 180 +- .../civicrm/CRM/Utils/Mail/EmailProcessor.php | 8 +- www/modules/civicrm/CRM/Utils/Money.php | 4 +- www/modules/civicrm/CRM/Utils/REST.php | 6 +- www/modules/civicrm/CRM/Utils/Recent.php | 2 +- www/modules/civicrm/CRM/Utils/SQL.php | 2 +- .../CRM/Utils/SQL/EscapeStringTrait.php | 4 +- www/modules/civicrm/CRM/Utils/SQL/Insert.php | 2 +- .../civicrm/CRM/Utils/SQL/TempTable.php | 4 +- www/modules/civicrm/CRM/Utils/Schema.php | 341 + www/modules/civicrm/CRM/Utils/String.php | 55 +- www/modules/civicrm/CRM/Utils/System.php | 16 +- .../civicrm/CRM/Utils/System/Backdrop.php | 7 +- www/modules/civicrm/CRM/Utils/System/Base.php | 25 +- .../civicrm/CRM/Utils/System/Drupal8.php | 38 +- .../civicrm/CRM/Utils/System/DrupalBase.php | 7 +- .../civicrm/CRM/Utils/System/Joomla.php | 2 +- .../civicrm/CRM/Utils/System/Standalone.php | 24 +- .../civicrm/CRM/Utils/System/UnitTests.php | 26 + .../civicrm/CRM/Utils/System/WordPress.php | 77 +- www/modules/civicrm/CRM/Utils/Time.php | 2 +- www/modules/civicrm/CRM/Utils/Token.php | 51 +- www/modules/civicrm/CRM/Utils/Url.php | 83 + www/modules/civicrm/Civi.php | 25 + www/modules/civicrm/Civi/API/Kernel.php | 2 +- www/modules/civicrm/Civi/API/SelectQuery.php | 2 +- .../API/Subscriber/DynamicFKAuthorization.php | 9 +- .../Civi/ActionSchedule/MappingBase.php | 14 +- .../Api4/Action/CustomValue/GetFields.php | 29 - .../civicrm/Civi/Api4/Action/GetLinks.php | 7 + www/modules/civicrm/Civi/Api4/CustomValue.php | 8 +- .../Subscriber/ValidateFieldsSubscriber.php | 8 +- www/modules/civicrm/Civi/Api4/ExampleData.php | 2 +- www/modules/civicrm/Civi/Api4/Extension.php | 34 + .../Civi/Api4/Generic/AbstractAction.php | 2 +- .../Civi/Api4/Generic/AbstractEntity.php | 8 +- .../Api4/Generic/BasicGetFieldsAction.php | 6 + .../Civi/Api4/Generic/DAOGetAction.php | 4 + .../Civi/Api4/Generic/DAOGetFieldsAction.php | 37 +- .../civicrm/Civi/Api4/Generic/Result.php | 29 +- .../Api4/Generic/Traits/DAOActionTrait.php | 48 +- .../Traits/GroupAndHavingParamTrait.php | 5 +- .../Traits/SavedSearchInspectorTrait.php | 4 +- www/modules/civicrm/Civi/Api4/Permission.php | 6 + .../Api4/Provider/ActionObjectProvider.php | 14 +- .../Api4/Provider/CustomEntityProvider.php | 38 +- .../civicrm/Civi/Api4/Query/Api4Query.php | 51 +- .../Civi/Api4/Query/Api4SelectQuery.php | 36 +- www/modules/civicrm/Civi/Api4/Queue.php | 2 +- www/modules/civicrm/Civi/Api4/Route.php | 2 +- .../PhoneAutocompleteProvider.php | 54 + .../Service/Links/ActivityLinksProvider.php | 2 +- .../Schema/Joinable/CustomGroupJoinable.php | 57 +- .../Service/Schema/Joinable/ExtraJoinable.php | 3 +- .../Api4/Service/Schema/Joinable/Joinable.php | 60 +- .../Api4/Service/Schema/SchemaMapBuilder.php | 89 +- .../Civi/Api4/Service/Spec/FieldSpec.php | 43 +- .../Spec/Provider/ContactGetSpecProvider.php | 4 +- .../Spec/Provider/CustomValueSpecProvider.php | 2 +- .../Provider/EntityTagFilterSpecProvider.php | 6 +- .../Generic/SpecProviderInterface.php | 5 +- .../Spec/Provider/TitleFieldSpecProvider.php | 2 +- .../Civi/Api4/Service/Spec/RequestSpec.php | 89 +- .../Civi/Api4/Service/Spec/SpecFormatter.php | 57 +- .../Civi/Api4/Service/Spec/SpecGatherer.php | 284 +- .../Api4/{Provider.php => SmsProvider.php} | 2 +- www/modules/civicrm/Civi/Api4/UserJob.php | 1 + .../civicrm/Civi/Api4/Utils/CoreUtil.php | 17 +- www/modules/civicrm/Civi/Core/Container.php | 17 +- .../Core/SqlTrigger/TimestampTriggers.php | 8 +- www/modules/civicrm/Civi/Core/Url.php | 121 +- www/modules/civicrm/Civi/Crypto/CryptoJwt.php | 4 +- .../civicrm/Civi/Crypto/CryptoRegistry.php | 4 +- .../civicrm/Civi/Crypto/CryptoToken.php | 4 +- .../FkEntityDeleteSubscriber.php | 70 + .../civicrm/Civi/Payment/PropertyBag.php | 5 +- .../Civi/Schema/EntityMetadataBase.php | 32 + .../Civi/Schema/EntityMetadataInterface.php | 22 + .../civicrm/Civi/Schema/EntityProvider.php | 90 + .../civicrm/Civi/Schema/EntityRepository.php | 79 + .../Civi/Schema/EntityStorageInterface.php | 20 + .../Civi/Schema/LegacySqlEntityMetadata.php | 193 + .../civicrm/Civi/Schema/SqlEntityMetadata.php | 47 + .../Schema/SqlEntityStorage.php} | 30 +- .../Civi/Schema/Traits/DataTypeSpecTrait.php | 10 +- .../Civi/Schema/Traits/OptionsSpecTrait.php | 23 +- www/modules/civicrm/Civi/Test.php | 6 +- .../civicrm/Civi/Test/Api3TestTrait.php | 2 +- .../civicrm/Civi/Test/Api4TestTrait.php | 3 +- .../Test/CiviEnvBuilder/CoreSchemaStep.php | 7 +- www/modules/civicrm/Civi/Test/Data.php | 2 +- .../civicrm/Civi/Test/EntityExample.php | 2 +- www/modules/civicrm/Civi/Test/EntityTrait.php | 5 + .../civicrm/Civi/Test/EventTestTrait.php | 32 +- .../Civi/Test/ExampleData/Contact/Barb.php | 7 + .../ExampleData/Contribution/Euro5990.php | 1 + .../Civi/Test/ExampleData/Event/PaidEvent.php | 1 + www/modules/civicrm/Civi/Test/FormTrait.php | 11 +- www/modules/civicrm/Civi/Test/FormWrapper.php | 50 +- .../Civi/Token/TokenCompatSubscriber.php | 44 +- .../civicrm/Civi/Token/TokenProcessor.php | 52 +- .../Traits/CustomFieldTrait.php | 40 +- .../civicrm/ang/api4Explorer/Explorer.html | 6 +- .../civicrm/ang/api4Explorer/Explorer.js | 7 +- www/modules/civicrm/ang/crmUi.js | 20 +- www/modules/civicrm/api/api.php | 2 +- www/modules/civicrm/api/v3/Contribution.php | 2 +- www/modules/civicrm/api/v3/Job.php | 1 + www/modules/civicrm/api/v3/Mailing.php | 207 +- www/modules/civicrm/api/v3/MailingJob.php | 8 +- www/modules/civicrm/api/v3/SmsProvider.php | 2 +- www/modules/civicrm/api/v3/utils.php | 16 +- www/modules/civicrm/backdrop/civicrm.info | 2 +- www/modules/civicrm/backdrop/civicrm.module | 97 +- .../civicrm/backdrop/civicrm_backdrop.css | 19 +- .../civicrm/backdrop/icons/civicrm.svg | 1 + .../civicrm_contact_ref.info | 2 +- .../civicrm_engage/civicrm_engage.info | 2 +- .../civicrm_group_roles.info | 2 +- .../civicrm_group_roles.module | 1 + .../civicrm_member_roles.info | 2 +- .../modules/civicrm_rules/civicrm_rules.info | 2 +- .../modules/civicrmtheme/civicrmtheme.info | 2 +- .../modules/civicrmtheme/civicrmtheme.install | 95 + .../layout.layout.civicrm_admin_default.json | 16 +- www/modules/civicrm/civicrm-version.php | 2 +- www/modules/civicrm/composer.json | 24 +- www/modules/civicrm/composer.lock | 329 +- www/modules/civicrm/css/civicrm.css | 13 +- www/modules/civicrm/css/joomla.css | 422 +- www/modules/civicrm/css/menubar-joomla.css | 10 +- www/modules/civicrm/deleted-files-list.json | 416 +- .../Civi/AfformAdmin/AfformAdminInjector.php | 101 +- .../Civi/AfformAdmin/AfformAdminMeta.php | 12 +- .../ang/afAdminFormSubmissionList.aff.php | 3 +- .../ext/afform/admin/ang/afGuiEditor.css | 1 + .../ang/afGuiEditor/afGuiEntity.component.js | 4 + .../admin/ang/afGuiEditor/afGuiEntity.html | 32 +- .../afGuiEditor/afGuiFieldValue.directive.js | 7 +- .../ang/afGuiEditor/afGuiSearch.component.js | 8 + .../admin/ang/afGuiEditor/afGuiSearch.html | 2 +- .../admin/ang/afGuiEditor/config-form.html | 2 +- .../afGuiEditor/elements/afGuiField-menu.html | 20 +- .../elements/afGuiField.component.js | 56 +- www/modules/civicrm/ext/afform/admin/info.xml | 8 +- .../managed/FormSubmissionSavedSearch.mgd.php | 2 +- .../managed/Navigation_afform_admin.mgd.php | 2 - .../afform/admin/xml/Menu/afform_admin.xml | 2 +- .../core/CRM/Afform/Page/AfformBase.php | 17 +- .../Civi/Afform/Behavior/ContactAutofill.php | 4 +- .../afform/core/Civi/Afform/FormDataModel.php | 2 +- .../Api4/Action/Afform/AbstractProcessor.php | 130 +- .../core/Civi/Api4/Action/Afform/Get.php | 30 +- .../Civi/Api4/Action/Afform/GetOptions.php | 2 +- .../core/Civi/Api4/Action/Afform/Submit.php | 133 +- .../Civi/Api4/Action/Afform/SubmitFile.php | 46 +- .../ext/afform/core/Civi/Api4/Afform.php | 2 +- .../afform/core/Civi/Api4/AfformBehavior.php | 2 +- .../core/Civi/Api4/AfformSubmission.php | 2 +- .../AfformAutocompleteSubscriber.php | 2 +- .../civicrm/ext/afform/core/afform.php | 17 +- .../afform/core/ang/af/afField.component.js | 25 +- .../ext/afform/core/ang/af/fields/File.html | 8 +- .../ext/afform/core/ang/af/fields/Select.html | 2 +- .../civicrm/ext/afform/core/ang/afCore.js | 1 + .../core/ang/afblockParticipantNote.aff.html | 3 + .../core/ang/afblockParticipantNote.aff.php | 7 + www/modules/civicrm/ext/afform/core/info.xml | 8 +- .../afform/core/managed/AfformType.mgd.php | 48 - .../ext/afform/core/sql/auto_install.sql | 2 +- .../phpunit/Civi/Afform/AfformGetTest.php | 33 + www/modules/civicrm/ext/afform/html/README.md | 45 +- .../ext/afform/html/images/screenshot.png | Bin 11775 -> 0 bytes www/modules/civicrm/ext/afform/html/info.xml | 6 +- www/modules/civicrm/ext/afform/mock/info.xml | 6 +- .../api/v4/Afform/AfformFileUploadTest.php | 2 +- .../api/v4/Afform/AfformRoutingTest.php | 4 +- www/modules/civicrm/ext/authx/authx.php | 16 +- .../civicrm/ext/authx/images/screenshot.png | Bin 11775 -> 0 bytes www/modules/civicrm/ext/authx/info.xml | 6 +- .../phpunit/Civi/Authx/AbstractFlowsTest.php | 2 +- .../ang/afsearchCampaignDashboard.aff.html | 16 +- .../civicrm/ext/civi_campaign/info.xml | 6 +- .../SavedSearch_Administer_Campaigns.mgd.php | 4 +- .../SavedSearch_Administer_Petitions.mgd.php | 4 +- .../SavedSearch_Administer_Surveys.mgd.php | 4 +- .../civicrm/ext/civi_case/ang/crmCaseType.js | 4 +- .../ang/crmCaseType/caseTypeDetails.html | 4 +- .../civi_case/ang/crmCaseType/rolesTable.html | 4 +- www/modules/civicrm/ext/civi_case/info.xml | 6 +- .../Service/ContributionTasksProvider.php | 52 + .../civicrm/ext/civi_contribute/info.xml | 6 +- .../Links/ParticipantLinksProvider.php | 72 + .../Spec/Provider/EventGetSpecProvider.php | 18 +- www/modules/civicrm/ext/civi_event/info.xml | 6 +- .../Civi/Api4/Action/Mailing/CreateAction.php | 23 + .../Api4/Action/Mailing/MailingSaveTrait.php} | 25 +- .../Civi/Api4/Action/Mailing/SaveAction.php | 23 + .../Civi/Api4/Action/Mailing/UpdateAction.php | 23 + .../ext/civi_mail/Civi/Api4/Mailing.php | 31 + .../Civi/Api4/Service/MailTasksProvider.php | 45 + .../ang/afsearchEmailBounceHistory.aff.html | 2 +- .../civi_mail/ang/crmMailing/BlockPreview.js | 2 +- .../civi_mail/ang/crmMailing/BlockReview.js | 2 +- .../ang/crmMailing/EditMailingCtrl.js | 2 +- .../civi_mail/ang/crmMailing/EditRecipCtrl.js | 2 +- .../crmMailing/EditRecipOptionsDialogCtrl.js | 2 +- .../civi_mail/ang/crmMailing/EmailAddrCtrl.js | 2 +- .../civi_mail/ang/crmMailing/EmailBodyCtrl.js | 2 +- .../ang/crmMailing/MsgTemplateCtrl.js | 2 +- .../ang/crmMailing/PreviewComponentCtrl.js | 2 +- .../crmMailing/PreviewComponentDialogCtrl.js | 2 +- .../crmMailing/PreviewMailingDialogCtrl.js | 2 +- .../ang/crmMailing/PreviewRecipCtrl.js | 2 +- .../crmMailing/SaveMsgTemplateDialogCtrl.js | 2 +- .../ext/civi_mail/ang/crmMailing/services.js | 4 +- .../ang/crmMailingAB/BlockMailing.js | 2 +- .../civi_mail/ang/crmMailingAB/BlockSetup.js | 2 +- .../civi_mail/ang/crmMailingAB/EditCtrl.js | 2 +- .../civi_mail/ang/crmMailingAB/ListCtrl.js | 2 +- .../civi_mail/ang/crmMailingAB/ReportCtrl.js | 2 +- .../ext/civi_mail/ang/crmMailingAB/Slider.js | 2 +- .../ang/crmMailingAB/WinnerDialogCtrl.js | 2 +- www/modules/civicrm/ext/civi_mail/info.xml | 6 +- .../Service/Links/MembershipLinksProvider.php | 137 + .../Links/MembershipStatusLinksProvider.php | 58 + .../Provider/MembershipGetSpecProvider.php | 68 + www/modules/civicrm/ext/civi_member/info.xml | 6 +- www/modules/civicrm/ext/civi_pledge/info.xml | 6 +- www/modules/civicrm/ext/civi_report/info.xml | 6 +- .../ang/afformTabMember.aff.html | 16 + .../ang/afformTabMember.aff.php | 19 + .../ang/afsearchLabelFormats.aff.html | 2 +- .../ang/afsearchPrintPagePDFFormats.aff.html | 8 + .../ang/afsearchPrintPagePDFFormats.aff.php | 9 + .../ang/afsearchRelationshipTypes.aff.html | 4 +- .../ang/afsearchSMSProvider.aff.html | 7 + .../ang/afsearchSMSProvider.aff.php | 13 + .../ang/afsearchTabMember.aff.html | 10 + .../ang/afsearchTabMember.aff.php | 20 + .../ang/afsearchTabParticipant.aff.html | 3 + .../ang/afsearchTabParticipant.aff.php | 16 + .../civicrm/ext/civicrm_admin_ui/info.xml | 22 +- .../managed/SavedSearch_ACL_Roles.mgd.php | 12 +- ...nister_Assigned_Financial_Accounts.mgd.php | 7 +- ...vedSearch_Administer_Custom_Fields.mgd.php | 4 +- ...vedSearch_Administer_Custom_Groups.mgd.php | 63 +- ...arch_Administer_Financial_Accounts.mgd.php | 9 +- ...dSearch_Administer_Financial_Types.mgd.php | 9 +- ...edSearch_Administer_Location_Types.mgd.php | 4 +- ...avedSearch_Administer_Manage_Group.mgd.php | 4 +- ...arch_Administer_Payment_Processors.mgd.php | 9 +- ...vedSearch_Administer_ProfileFields.mgd.php | 4 +- .../SavedSearch_Administer_Profiles.mgd.php | 4 +- ...arch_Administer_Relationship_Types.mgd.php | 4 +- ...rch_Administer_Scheduled_Reminders.mgd.php | 12 +- ...SavedSearch_Contact_Summary_Events.mgd.php | 252 + ...ch_Contact_Summary_Membership_Type.mgd.php | 159 + ...Search_Contact_Summary_Memberships.mgd.php | 533 + ...ers_Footers_and_Automated_Messages.mgd.php | 12 +- .../managed/SavedSearch_Manage_ACLs.mgd.php | 4 +- ...edSearch_Manage_Contribution_Pages.mgd.php | 9 +- .../SavedSearch_Manage_Mail_Accounts.mgd.php | 6 +- .../SavedSearch_Manage_Premiums.mgd.php | 12 +- .../SavedSearch_Manage_Scheduled_Jobs.mgd.php | 4 +- ...SavedSearch_Print_Page_PDF_Formats.mgd.php | 181 + .../managed/SavedSearch_SMS_Provider.mgd.php | 185 + .../ang/afsearchFindMemberships.aff.html | 25 + .../ang/afsearchFindMemberships.aff.json | 31 + .../civicrm/ext/civicrm_search_ui/info.xml | 18 +- .../SavedSearch_Find_Memberships.mgd.php | 309 + .../ext/civigrant/CRM/Grant/Form/Grant.php | 44 +- .../civicrm/ext/civigrant/civigrant.php | 12 +- www/modules/civicrm/ext/civigrant/info.xml | 8 +- .../ext/civigrant/sql/auto_install.sql | 2 +- .../templates/CRM/Grant/Form/Grant.tpl | 2 +- .../templates/CRM/Grant/Form/Search.tpl | 10 +- .../Civi/Api4/Import/ImportSpecProvider.php | 37 +- .../ext/civiimport/Civi/BAO/Import.php | 27 - .../Managed/UserJobSearches.mgd.php | 88 + www/modules/civicrm/ext/civiimport/README.md | 45 +- .../civiimport/ang/afsearchMyImports.aff.html | 2 +- .../ext/civiimport/images/screenshot.png | Bin 11775 -> 0 bytes www/modules/civicrm/ext/civiimport/info.xml | 8 +- www/modules/civicrm/ext/ckeditor4/README.md | 44 +- .../ext/ckeditor4/images/screenshot.png | Bin 11775 -> 0 bytes www/modules/civicrm/ext/ckeditor4/info.xml | 8 +- .../ext/contributioncancelactions/info.xml | 6 +- .../tests/phpunit/CancelTest.php | 16 +- .../ext/elavon/CRM/Core/Payment/Elavon.php | 3 +- www/modules/civicrm/ext/elavon/info.xml | 6 +- .../phpunit/CRM/Core/Payment/ElavonTest.php | 2 +- .../ext/eventcart/CRM/Event/Cart/BAO/Cart.php | 6 - .../CRM/Event/Cart/BAO/EventInCart.php | 6 - .../CRM/Event/Cart/BAO/MerParticipant.php | 5 - .../eventcart/CRM/Event/Cart/Form/Cart.php | 2 +- .../Form/Checkout/ParticipantsAndPrices.php | 27 +- .../CRM/Event/Cart/Form/Checkout/Payment.php | 6 +- .../CRM/Event/Form/ManageEvent/Conference.php | 3 +- www/modules/civicrm/ext/eventcart/README.md | 8 +- .../ext/eventcart/images/screenshot.png | Bin 11775 -> 0 bytes www/modules/civicrm/ext/eventcart/info.xml | 8 +- www/modules/civicrm/ext/ewaysingle/README.md | 21 - .../ext/ewaysingle/images/screenshot.png | Bin 11775 -> 0 bytes www/modules/civicrm/ext/ewaysingle/info.xml | 6 +- .../civicrm/ext/financialacls/README.md | 45 +- .../ext/financialacls/financialacls.php | 66 +- .../ext/financialacls/images/screenshot.png | Bin 11775 -> 0 bytes .../civicrm/ext/financialacls/info.xml | 6 +- .../Financialacls/BuildAmountHookTest.php | 11 +- .../Civi/Financialacls/FinancialTypeTest.php | 8 +- .../civicrm/ext/flexmailer/flexmailer.php | 14 + www/modules/civicrm/ext/flexmailer/info.xml | 6 +- .../ext/flexmailer/src/API/MailingPreview.php | 6 - .../src/ClickTracker/HtmlClickTracker.php | 7 +- .../src/ClickTracker/TextClickTracker.php | 7 +- .../ext/flexmailer/src/Listener/Abdicator.php | 7 +- .../flexmailer/src/Listener/Attachments.php | 8 +- .../flexmailer/src/Listener/BaseListener.php | 22 +- .../flexmailer/src/Listener/BasicHeaders.php | 11 +- .../flexmailer/src/Listener/BounceTracker.php | 11 +- .../src/Listener/DefaultBatcher.php | 54 +- .../src/Listener/DefaultComposer.php | 7 +- .../flexmailer/src/Listener/DefaultSender.php | 9 +- .../flexmailer/src/Listener/HookAdapter.php | 8 +- .../flexmailer/src/Listener/IsActiveTrait.php | 36 + .../flexmailer/src/Listener/OpenTracker.php | 8 +- .../src/Listener/RequiredFields.php | 18 +- .../src/Listener/RequiredTokens.php | 23 +- .../flexmailer/src/Listener/TestPrefix.php | 8 +- .../ext/flexmailer/src/Listener/ToHeader.php | 8 +- .../civicrm/ext/flexmailer/src/Services.php | 37 - .../Civi/FlexMailer/ClickTrackerTest.php | 11 + .../Civi/FlexMailer/FlexMailerSystemTest.php | 6 +- .../civicrm/ext/greenwich/dist/bootstrap3.css | 12 +- .../ext/greenwich/dist/bootstrap3.min.css | 2 +- www/modules/civicrm/ext/greenwich/info.xml | 6 +- .../ext/greenwich/scss/_greenwich.scss | 2 +- .../iatspayments/CRM/Core/Payment/Faps.php | 2 +- .../CRM/Core/Payment/iATSService.php | 4 +- .../iatspayments/CRM/Iats/Form/Settings.php | 191 - .../ext/iatspayments/CRM/Iats/Upgrader.php | 10 +- .../ext/iatspayments/CRM/Iats/Utils.php | 56 + .../ext/iatspayments/api/v3/Job/Fapsquery.php | 2 +- .../api/v3/Job/Iatsrecurringcontributions.php | 2 +- .../iatspayments/api/v3/Job/Iatsreport.php | 2 +- .../iatspayments/api/v3/Job/Iatsverify.php | 2 +- www/modules/civicrm/ext/iatspayments/iats.php | 42 +- www/modules/civicrm/ext/iatspayments/info.xml | 9 +- .../ext/iatspayments/release-notes/1.8.0.md | 2 +- .../ext/iatspayments/release-notes/1.9.0.md | 15 + .../iatspayments/settings/iats.setting.php | 147 + .../Iats/BillingBlockDirectDebitExtra_USD.tpl | 8 +- .../CRM/Iats/Form/IATSOneTimeCharge.tpl | 5 - .../templates/CRM/Iats/Form/Settings.tpl | 18 - .../tests/phpunit/CRM/Iats/SettingsTest.php | 61 + .../ext/iatspayments/xml/Menu/iats.xml | 4 +- .../iframe/CRM/Iframe/Page/InstallPage.php | 20 + .../civicrm/ext/iframe/CRM/Iframe/Utils.php | 23 + .../civicrm/ext/iframe/Civi/Api4/Iframe.php | 32 + .../ext/iframe/Civi/Iframe/Cosession.php | 230 + .../Civi/Iframe/EntryPoint/Backdrop.php | 39 + .../iframe/Civi/Iframe/EntryPoint/Drupal.php | 39 + .../iframe/Civi/Iframe/EntryPoint/Drupal8.php | 56 + .../civicrm/ext/iframe/Civi/Iframe/Iframe.php | 40 + .../civicrm/ext/iframe/Civi/Iframe/Router.php | 126 + .../ext/iframe/Civi/Iframe/ScriptManager.php | 176 + www/modules/civicrm/ext/iframe/README.md | 127 + .../civicrm/ext/iframe/iframe.civix.php | 200 + www/modules/civicrm/ext/iframe/iframe.php | 40 + www/modules/civicrm/ext/iframe/info.xml | 44 + .../ext/iframe/lib/IframeDrupalKernel.php | 33 + .../iframe/lib/IframeSessionConfiguration.php | 14 + .../ext/iframe/settings/iframe.setting.php | 71 + .../templates/CRM/Iframe/Page/InstallPage.tpl | 25 + .../iframe/templates/iframe-basic-page.tpl | 12 + .../civicrm/ext/iframe/xml/Menu/iframe.xml | 9 + .../Contact/Form/Search/Custom/FullText.php | 2 + .../ext/legacycustomsearches/README.md | 45 +- .../images/screenshot.png | Bin 11775 -> 0 bytes .../civicrm/ext/legacycustomsearches/info.xml | 8 +- .../legacycustomsearches.php | 6 +- .../CRM/Contact/Form/Search/Custom.tpl | 10 +- .../Form/Search/Custom/ActivitySearch.tpl | 10 +- .../Form/Search/Custom/ContribSYBNT.tpl | 10 +- .../Search/Custom/ContributionAggregate.tpl | 10 +- .../Form/Search/Custom/EventDetails.tpl | 8 +- .../Search/Custom/MultipleValuesCriteria.tpl | 10 +- .../Contact/Form/Search/Custom/Proximity.tpl | 10 +- .../civicrm/ext/message_admin/README.md | 45 +- .../ext/message_admin/images/screenshot.png | Bin 11775 -> 0 bytes .../civicrm/ext/message_admin/info.xml | 8 +- .../CRM/OAuth/BAO/OAuthContactToken.php | 21 +- .../Civi/Api4/OAuthContactToken.php | 7 +- www/modules/civicrm/ext/oauth-client/info.xml | 8 +- .../civicrm/ext/oauth-client/oauth_client.php | 21 +- .../ext/oauth-client/sql/auto_install.sql | 6 +- .../ext/oauth-client/sql/upgrade_0001.sql | 2 +- .../templates/CRM/OAuth/Page/Return.tpl | 24 +- .../phpunit/api/v4/OAuthSessionTokenTest.php | 2 +- .../ext/oembed/CRM/Oembed/Page/Oembed.php | 100 + .../oembed/CRM/Oembed/Page/OembedShare.php | 34 + .../civicrm/ext/oembed/Civi/Oembed/Oembed.php | 131 + .../oembed/Civi/Oembed/OembedDiscovery.php | 68 + www/modules/civicrm/ext/oembed/LICENSE.txt | 667 ++ www/modules/civicrm/ext/oembed/README.md | 30 + .../ext/oembed/ang/crmOembedSharing.ang.php | 23 + .../ext/oembed/ang/crmOembedSharing.css | 3 + .../ext/oembed/ang/crmOembedSharing.js | 4 + .../ext/oembed/ang/crmOembedSharing/main.html | 43 + .../ext/oembed/ang/crmOembedSharing/main.js | 41 + www/modules/civicrm/ext/oembed/info.xml | 48 + .../civicrm/ext/oembed/oembed.civix.php | 200 + www/modules/civicrm/ext/oembed/oembed.php | 32 + .../civicrm/ext/oembed/phpunit.xml.dist | 18 + .../ext/oembed/settings/oembed.setting.php | 34 + .../templates/CRM/Oembed/Page/Oembed.tpl | 1 + .../templates/CRM/Oembed/Page/OembedShare.tpl | 1 + .../CRM/Oembed/Page/OembedDiscoveryTest.php | 107 + .../phpunit/CRM/Oembed/Page/OembedTest.php | 96 + .../ext/oembed/tests/phpunit/bootstrap.php | 65 + .../civicrm/ext/oembed/xml/Menu/oembed.xml | 17 + www/modules/civicrm/ext/payflowpro/README.md | 9 - .../ext/payflowpro/images/screenshot.png | Bin 11775 -> 0 bytes www/modules/civicrm/ext/payflowpro/info.xml | 6 +- www/modules/civicrm/ext/recaptcha/info.xml | 8 +- .../Civi/Search/ActionMapping.php | 2 +- .../ext/scheduled_communications/info.xml | 6 +- .../CRM/Search/BAO/SearchDisplay.php | 64 +- .../SearchDisplay/AbstractRunAction.php | 101 +- .../Api4/Action/SearchDisplay/GetDefault.php | 14 + .../Action/SearchDisplay/GetSearchTasks.php | 32 +- .../Civi/Api4/Action/SearchDisplay/Run.php | 2 +- .../Subscriber/DefaultDisplaySubscriber.php | 8 +- .../SearchDisplayTasksSubscriber.php | 10 +- .../ext/search_kit/Civi/Api4/SKEntity.php | 4 +- .../search_kit/Civi/Api4/SearchDisplay.php | 2 +- .../search_kit/Civi/Api4/SearchSegment.php | 2 +- .../Spec/Provider/SKEntitySpecProvider.php | 4 +- .../ext/search_kit/Civi/BAO/SK_Entity.php | 19 - .../ext/search_kit/Civi/Search/Admin.php | 40 +- .../crmSearchAdmin.component.js | 2 +- .../crmSearchAdmin/crmSearchAdminFields.html | 2 +- .../displays/colType/field.html | 2 +- .../crmSearchAdminSearchListing.component.js | 2 +- .../crmSearchAdminSegment.component.js | 4 + .../searchSegment/crmSearchAdminSegment.html | 2 +- .../crmSearchDisplayTableBody.html | 4 +- .../crmSearchTasks/crmSearchInput/date.html | 2 +- .../crmSearchTasks/crmSearchInput/select.html | 2 +- www/modules/civicrm/ext/search_kit/info.xml | 8 +- .../managed/Navigation_search_kit.mgd.php | 1 - .../civicrm/ext/search_kit/search_kit.php | 16 +- .../ext/search_kit/sql/auto_install.sql | 4 +- .../api/v4/SearchDisplay/SearchRunTest.php | 64 +- .../v4/SearchSegment/SearchSegmentTest.php | 10 +- .../ext/sequentialcreditnotes/info.xml | 6 +- .../CRM/Standaloneusers/BAO/Role.php | 45 +- .../CRM/Standaloneusers/BAO/User.php | 86 +- .../CRM/Standaloneusers/DAO/User.php | 21 +- .../CRM/Standaloneusers/DAO/UserRole.php | 4 +- .../CRM/Standaloneusers/Upgrader.php | 27 +- .../Api4/Action/User/SendPasswordReset.php | 14 +- .../Civi/Api4/Action/User/Update.php | 32 + .../Civi/Api4/Action/User/WriteTrait.php | 11 +- .../ext/standaloneusers/Civi/Api4/User.php | 21 +- .../standaloneusers/Civi/Api4/UserRole.php | 10 + .../Civi/Standalone/Security.php | 57 +- .../Civi/Standalone/SessionHandler.php | 2 +- .../civicrm/ext/standaloneusers/README.md | 9 + .../standaloneusers/ang/crmChangePassword.js | 76 +- .../crmChangePassword/crmChangePassword.html | 77 +- .../civicrm/ext/standaloneusers/info.xml | 8 +- .../SavedSearch_Administer_Roles.mgd.php | 4 +- ...avedSearch_Administer_UserAccounts.mgd.php | 12 +- .../ext/standaloneusers/sql/auto_install.sql | 10 +- .../ext/standaloneusers/sql/upgrade_5691.sql | 2 +- .../ext/standaloneusers/standaloneusers.php | 15 +- .../phpunit/Civi/Api4/Action/UserTest.php | 808 ++ .../phpunit/Civi/Standalone/SecurityTest.php | 224 +- .../xml/schema/CRM/Standaloneusers/User.xml | 8 +- .../civicrm/ext/user_dashboard/info.xml | 6 +- www/modules/civicrm/extern/ipn.php | 20 +- www/modules/civicrm/functions.php | 86 + www/modules/civicrm/js/Common.js | 32 +- www/modules/civicrm/js/model/crm.uf.js | 4 +- www/modules/civicrm/js/version.json | 2 +- ...arch_Contact_Summary_Relationships.mgd.php | 12 +- .../mixin/entity-types-php@2/mixin.php | 40 + .../lib/civimix-schema/pathload.main.php | 28 + .../civimix-schema/src/AutomaticUpgrader.php | 176 + .../lib/civimix-schema/src/CiviMixSchema.php | 46 + .../lib/civimix-schema/src/SchemaHelper.php | 82 + .../src/SchemaHelperInterface.php | 30 + .../lib/civimix-schema/src/SqlGenerator.php | 205 + www/modules/civicrm/mixin/lib/pathload-0.php | 711 ++ .../civicrm/mixin/lib/pathload.index.php | 15 + .../civicrm/mixin/setting-admin@1/mixin.php | 7 +- .../civicrm/mixin/smarty-v2@1/mixin.php | 45 +- www/modules/civicrm/mixin/smarty@1/mixin.php | 74 + .../civicrm/packages/Smarty/Smarty.class.php | 151 +- .../packages/kcfinder/integration/civicrm.php | 21 +- .../civicrm/packages/smarty4/composer.json | 5 + .../civicrm/packages/smarty4/composer.lock | 85 + .../packages/smarty4/vendor/autoload.php | 12 + .../smarty4/vendor/composer/ClassLoader.php | 572 ++ .../vendor/composer/InstalledVersions.php | 352 + .../packages/smarty4/vendor/composer/LICENSE | 21 + .../vendor/composer/autoload_classmap.php | 179 + .../vendor/composer/autoload_namespaces.php | 9 + .../smarty4/vendor/composer/autoload_psr4.php | 9 + .../smarty4/vendor/composer/autoload_real.php | 38 + .../vendor/composer/autoload_static.php | 189 + .../smarty4/vendor/composer/installed.json | 75 + .../smarty4/vendor/composer/installed.php | 32 + .../vendor/composer/platform_check.php | 26 + .../smarty4/vendor/smarty/smarty/CHANGELOG.md | 3625 +++++++ .../smarty4/vendor/smarty/smarty/LICENSE | 179 + .../smarty4/vendor/smarty/smarty/README.md | 20 + .../smarty4/vendor/smarty/smarty/SECURITY.md | 20 + .../vendor/smarty/smarty/changelog/.gitkeep | 0 .../vendor/smarty/smarty/composer.json | 49 + .../smarty/smarty/demo/configs/test.conf | 5 + .../vendor/smarty/smarty/demo/index.php | 35 + .../smarty/demo/plugins/cacheresource.apc.php | 85 + .../demo/plugins/cacheresource.memcache.php | 99 + .../demo/plugins/cacheresource.mysql.php | 183 + .../smarty/demo/plugins/cacheresource.pdo.php | 346 + .../demo/plugins/cacheresource.pdo_gzip.php | 42 + .../demo/plugins/resource.extendsall.php | 62 + .../smarty/demo/plugins/resource.mysql.php | 101 + .../smarty/demo/plugins/resource.mysqls.php | 77 + .../smarty/smarty/demo/templates/footer.tpl | 2 + .../smarty/smarty/demo/templates/header.tpl | 5 + .../smarty/smarty/demo/templates/index.tpl | 87 + .../vendor/smarty/smarty/docs/_config.yml | 1 + .../smarty/smarty/docs/appendixes/tips.md | 274 + .../smarty/docs/appendixes/troubleshooting.md | 104 + .../designers/chapter-debugging-console.md | 40 + .../smarty/docs/designers/config-files.md | 74 + .../designers/language-basic-syntax/index.md | 33 + .../language-escaping.md | 79 + .../language-basic-syntax/language-math.md | 28 + .../language-syntax-attributes.md | 49 + .../language-syntax-comments.md | 69 + .../language-syntax-functions.md | 40 + .../language-syntax-quotes.md | 54 + .../language-syntax-variables.md | 109 + .../language-builtin-functions/index.md | 35 + .../language-function-append.md | 49 + .../language-function-assign.md | 147 + .../language-function-block.md | 201 + .../language-function-call.md | 77 + .../language-function-capture.md | 81 + .../language-function-config-load.md | 88 + .../language-function-debug.md | 17 + .../language-function-extends.md | 37 + .../language-function-for.md | 91 + .../language-function-foreach.md | 389 + .../language-function-function.md | 89 + .../language-function-if.md | 120 + .../language-function-include.md | 187 + .../language-function-insert.md | 86 + .../language-function-ldelim.md | 51 + .../language-function-literal.md | 34 + .../language-function-nocache.md | 20 + .../language-function-section.md | 610 ++ .../language-function-setfilter.md | 43 + .../language-function-strip.md | 45 + .../language-function-while.md | 43 + .../designers/language-combining-modifiers.md | 32 + .../language-custom-functions/index.md | 19 + .../language-function-counter.md | 45 + .../language-function-cycle.md | 55 + .../language-function-debug.md | 14 + .../language-function-eval.md | 81 + .../language-function-fetch.md | 61 + .../language-function-html-checkboxes.md | 102 + .../language-function-html-image.md | 58 + .../language-function-html-options.md | 146 + .../language-function-html-radios.md | 104 + .../language-function-html-select-date.md | 118 + .../language-function-html-select-time.md | 100 + .../language-function-html-table.md | 93 + .../language-function-mailto.md | 61 + .../language-function-math.md | 99 + .../language-function-textformat.md | 182 + .../designers/language-modifiers/index.md | 122 + .../language-modifier-capitalize.md | 49 + .../language-modifier-cat.md | 36 + .../language-modifier-count-characters.md | 43 + .../language-modifier-count-paragraphs.md | 41 + .../language-modifier-count-sentences.md | 39 + .../language-modifier-count-words.md | 35 + .../language-modifier-date-format.md | 145 + .../language-modifier-default.md | 45 + .../language-modifier-escape.md | 78 + .../language-modifier-from-charset.md | 20 + .../language-modifier-indent.md | 67 + .../language-modifier-lower.md | 34 + .../language-modifier-nl2br.md | 37 + .../language-modifier-regex-replace.md | 55 + .../language-modifier-replace.md | 45 + .../language-modifier-spacify.md | 44 + .../language-modifier-string-format.md | 43 + .../language-modifier-strip-tags.md | 46 + .../language-modifier-strip.md | 42 + .../language-modifier-to-charset.md | 20 + .../language-modifier-truncate.md | 57 + .../language-modifier-unescape.md | 43 + .../language-modifier-upper.md | 33 + .../language-modifier-wordwrap.md | 73 + .../designers/language-variables/index.md | 36 + .../language-assigned-variables.md | 126 + .../language-config-variables.md | 79 + .../language-variable-scopes.md | 60 + .../language-variables-smarty.md | 156 + .../vendor/smarty/smarty/docs/features.md | 152 + .../smarty/smarty/docs/getting-started.md | 167 + .../vendor/smarty/smarty/docs/index.md | 50 + .../vendor/smarty/smarty/docs/philosophy.md | 107 + .../docs/programmers/advanced-features.md | 14 + .../advanced-features-objects.md | 99 + .../advanced-features-outputfilters.md | 43 + .../advanced-features-postfilters.md | 40 + .../advanced-features-prefilters.md | 36 + .../advanced-features-security.md | 144 + .../advanced-features-static-classes.md | 27 + .../advanced-features-streams.md | 15 + .../advanced-features-template-inheritance.md | 128 + .../advanced-features-template-settings.md | 32 + .../smarty/docs/programmers/api-functions.md | 64 + .../api-functions/api-add-config-dir.md | 49 + .../api-functions/api-add-plugins-dir.md | 42 + .../api-functions/api-add-template-dir.md | 49 + .../api-functions/api-append-by-ref.md | 46 + .../programmers/api-functions/api-append.md | 61 + .../api-functions/api-assign-by-ref.md | 42 + .../programmers/api-functions/api-assign.md | 84 + .../api-functions/api-clear-all-assign.md | 34 + .../api-functions/api-clear-all-cache.md | 37 + .../api-functions/api-clear-assign.md | 32 + .../api-functions/api-clear-cache.md | 60 + .../api-functions/api-clear-compiled-tpl.md | 44 + .../api-functions/api-clear-config.md | 35 + .../api-functions/api-compile-all-config.md | 61 + .../api-compile-all-templates.md | 71 + .../api-functions/api-config-load.md | 47 + .../api-functions/api-create-data.md | 52 + .../api-functions/api-create-template.md | 99 + .../api-functions/api-disable-security.md | 15 + .../programmers/api-functions/api-display.md | 82 + .../api-functions/api-enable-security.md | 41 + .../programmers/api-functions/api-fetch.md | 91 + .../api-functions/api-get-cache-dir.md | 23 + .../api-functions/api-get-compile-dir.md | 23 + .../api-functions/api-get-config-dir.md | 40 + .../api-functions/api-get-config-vars.md | 37 + .../api-functions/api-get-plugins-dir.md | 31 + .../api-get-registered-object.md | 36 + .../programmers/api-functions/api-get-tags.md | 40 + .../api-functions/api-get-template-dir.md | 40 + .../api-functions/api-get-template-vars.md | 37 + .../api-functions/api-is-cached.md | 81 + .../api-functions/api-load-filter.md | 42 + .../api-functions/api-mute-expected-errors.md | 21 + .../api-register-cacheresource.md | 40 + .../api-functions/api-register-class.md | 65 + .../api-register-default-plugin-handler.md | 93 + .../api-functions/api-register-filter.md | 45 + .../api-functions/api-register-object.md | 44 + .../api-functions/api-register-plugin.md | 110 + .../api-functions/api-register-resource.md | 46 + .../api-functions/api-set-cache-dir.md | 32 + .../api-functions/api-set-compile-dir.md | 32 + .../api-functions/api-set-config-dir.md | 47 + .../api-functions/api-set-plugins-dir.md | 46 + .../api-functions/api-set-template-dir.md | 46 + .../api-functions/api-template-exists.md | 59 + .../api-functions/api-test-install.md | 22 + .../api-unregister-cacheresource.md | 28 + .../api-functions/api-unregister-filter.md | 23 + .../api-functions/api-unregister-object.md | 17 + .../api-functions/api-unregister-plugin.md | 36 + .../api-functions/api-unregister-resource.md | 28 + .../smarty/docs/programmers/api-variables.md | 63 + .../variable-allow-php-templates.md | 18 + .../api-variables/variable-auto-literal.md | 17 + .../variable-autoload-filters.md | 21 + .../api-variables/variable-cache-dir.md | 35 + .../api-variables/variable-cache-id.md | 11 + .../api-variables/variable-cache-lifetime.md | 30 + .../api-variables/variable-cache-locking.md | 11 + .../variable-cache-modified-check.md | 12 + .../api-variables/variable-caching-type.md | 9 + .../api-variables/variable-caching.md | 38 + .../api-variables/variable-compile-check.md | 30 + .../api-variables/variable-compile-dir.md | 29 + .../api-variables/variable-compile-id.md | 44 + .../api-variables/variable-compile-locking.md | 7 + .../api-variables/variable-compiler-class.md | 6 + .../variable-config-booleanize.md | 8 + .../api-variables/variable-config-dir.md | 23 + .../variable-config-overwrite.md | 40 + .../variable-config-read-hidden.md | 8 + .../api-variables/variable-debug-template.md | 9 + .../api-variables/variable-debugging-ctrl.md | 20 + .../api-variables/variable-debugging.md | 17 + .../variable-default-config-handler-func.md | 50 + .../variable-default-config-type.md | 7 + .../variable-default-modifiers.md | 8 + .../variable-default-resource-type.md | 7 + .../variable-default-template-handler-func.md | 50 + .../variable-direct-access-security.md | 13 + .../api-variables/variable-error-reporting.md | 17 + .../api-variables/variable-escape-html.md | 21 + .../api-variables/variable-force-cache.md | 6 + .../api-variables/variable-force-compile.md | 9 + .../api-variables/variable-left-delimiter.md | 8 + .../api-variables/variable-locking-timeout.md | 7 + .../variable-merge-compiled-includes.md | 27 + .../api-variables/variable-plugins-dir.md | 28 + .../api-variables/variable-right-delimiter.md | 8 + .../api-variables/variable-smarty-debug-id.md | 9 + .../api-variables/variable-template-dir.md | 36 + .../api-variables/variable-trusted-dir.md | 8 + .../variable-use-include-path.md | 49 + .../api-variables/variable-use-sub-dirs.md | 31 + .../smarty/smarty/docs/programmers/caching.md | 24 + .../programmers/caching/caching-cacheable.md | 176 + .../programmers/caching/caching-custom.md | 296 + .../programmers/caching/caching-groups.md | 60 + .../caching/caching-multiple-caches.md | 87 + .../programmers/caching/caching-setting-up.md | 153 + .../smarty/smarty/docs/programmers/charset.md | 44 + .../smarty/smarty/docs/programmers/plugins.md | 44 + .../plugins/plugins-block-functions.md | 95 + .../plugins/plugins-compiler-functions.md | 66 + .../programmers/plugins/plugins-functions.md | 94 + .../docs/programmers/plugins/plugins-howto.md | 18 + .../programmers/plugins/plugins-inserts.md | 48 + .../programmers/plugins/plugins-modifiers.md | 86 + .../plugins/plugins-naming-conventions.md | 51 + .../plugins/plugins-outputfilters.md | 48 + .../plugins/plugins-prefilters-postfilters.md | 89 + .../programmers/plugins/plugins-resources.md | 128 + .../programmers/plugins/plugins-writing.md | 36 + .../smarty/docs/programmers/resources.md | 19 + .../programmers/resources/resources-custom.md | 111 + .../resources/resources-extends.md | 36 + .../programmers/resources/resources-file.md | 160 + .../resources/resources-streams.md | 27 + .../programmers/resources/resources-string.md | 73 + .../resources/template-resources.md | 130 + .../docs/programmers/smarty-constants.md | 26 + .../vendor/smarty/smarty/docs/upgrading.md | 38 + .../smarty_internal_configfilelexer.plex | 318 + .../lexer/smarty_internal_configfileparser.y | 346 + .../lexer/smarty_internal_templatelexer.plex | 687 ++ .../lexer/smarty_internal_templateparser.y | 1272 +++ .../vendor/smarty/smarty/libs/Autoloader.php | 111 + .../smarty/smarty/libs/Smarty.class.php | 1405 +++ .../vendor/smarty/smarty/libs/bootstrap.php | 16 + .../vendor/smarty/smarty/libs/debug.tpl | 173 + .../vendor/smarty/smarty/libs/functions.php | 51 + .../smarty/libs/plugins/block.textformat.php | 121 + .../smarty/libs/plugins/function.counter.php | 62 + .../smarty/libs/plugins/function.cycle.php | 92 + .../smarty/libs/plugins/function.fetch.php | 204 + .../libs/plugins/function.html_checkboxes.php | 286 + .../libs/plugins/function.html_image.php | 158 + .../libs/plugins/function.html_options.php | 230 + .../libs/plugins/function.html_radios.php | 266 + .../plugins/function.html_select_date.php | 395 + .../plugins/function.html_select_time.php | 354 + .../libs/plugins/function.html_table.php | 164 + .../smarty/libs/plugins/function.mailto.php | 142 + .../smarty/libs/plugins/function.math.php | 142 + .../libs/plugins/modifier.capitalize.php | 147 + .../smarty/libs/plugins/modifier.count.php | 36 + .../libs/plugins/modifier.date_format.php | 86 + .../libs/plugins/modifier.debug_print_var.php | 103 + .../smarty/libs/plugins/modifier.escape.php | 189 + .../smarty/libs/plugins/modifier.explode.php | 25 + .../smarty/libs/plugins/modifier.implode.php | 15 + .../libs/plugins/modifier.mb_wordwrap.php | 71 + .../libs/plugins/modifier.number_format.php | 26 + .../libs/plugins/modifier.regex_replace.php | 55 + .../smarty/libs/plugins/modifier.replace.php | 37 + .../smarty/libs/plugins/modifier.spacify.php | 26 + .../smarty/libs/plugins/modifier.truncate.php | 62 + .../libs/plugins/modifiercompiler.cat.php | 28 + .../modifiercompiler.count_characters.php | 32 + .../modifiercompiler.count_paragraphs.php | 26 + .../modifiercompiler.count_sentences.php | 26 + .../plugins/modifiercompiler.count_words.php | 31 + .../libs/plugins/modifiercompiler.default.php | 32 + .../libs/plugins/modifiercompiler.escape.php | 87 + .../plugins/modifiercompiler.from_charset.php | 30 + .../libs/plugins/modifiercompiler.indent.php | 30 + .../plugins/modifiercompiler.json_encode.php | 11 + .../libs/plugins/modifiercompiler.lower.php | 29 + .../libs/plugins/modifiercompiler.nl2br.php | 23 + .../libs/plugins/modifiercompiler.noprint.php | 20 + .../libs/plugins/modifiercompiler.round.php | 23 + .../plugins/modifiercompiler.str_repeat.php | 23 + .../modifiercompiler.string_format.php | 24 + .../libs/plugins/modifiercompiler.strip.php | 30 + .../plugins/modifiercompiler.strip_tags.php | 28 + .../libs/plugins/modifiercompiler.strlen.php | 23 + .../libs/plugins/modifiercompiler.substr.php | 12 + .../plugins/modifiercompiler.to_charset.php | 30 + .../plugins/modifiercompiler.unescape.php | 52 + .../libs/plugins/modifiercompiler.upper.php | 28 + .../plugins/modifiercompiler.wordwrap.php | 39 + .../plugins/outputfilter.trimwhitespace.php | 89 + .../plugins/shared.escape_special_chars.php | 26 + .../plugins/shared.literal_compiler_param.php | 35 + .../libs/plugins/shared.make_timestamp.php | 49 + .../libs/plugins/shared.mb_str_replace.php | 87 + .../smarty/libs/plugins/shared.mb_unicode.php | 51 + .../variablefilter.htmlspecialchars.php | 19 + .../libs/sysplugins/smarty_cacheresource.php | 219 + .../smarty_cacheresource_custom.php | 297 + .../smarty_cacheresource_keyvaluestore.php | 538 + .../smarty/libs/sysplugins/smarty_data.php | 68 + .../libs/sysplugins/smarty_internal_block.php | 90 + .../smarty_internal_cacheresource_file.php | 235 + .../smarty_internal_compile_append.php | 52 + .../smarty_internal_compile_assign.php | 96 + .../smarty_internal_compile_block.php | 189 + .../smarty_internal_compile_block_child.php | 24 + .../smarty_internal_compile_block_parent.php | 31 + .../smarty_internal_compile_break.php | 117 + .../smarty_internal_compile_call.php | 89 + .../smarty_internal_compile_capture.php | 105 + .../smarty_internal_compile_child.php | 79 + .../smarty_internal_compile_config_load.php | 96 + .../smarty_internal_compile_continue.php | 25 + .../smarty_internal_compile_debug.php | 40 + .../smarty_internal_compile_eval.php | 70 + .../smarty_internal_compile_extends.php | 96 + .../smarty_internal_compile_for.php | 164 + .../smarty_internal_compile_foreach.php | 343 + .../smarty_internal_compile_function.php | 236 + .../sysplugins/smarty_internal_compile_if.php | 207 + .../smarty_internal_compile_include.php | 347 + .../smarty_internal_compile_insert.php | 157 + .../smarty_internal_compile_ldelim.php | 37 + .../smarty_internal_compile_make_nocache.php | 62 + .../smarty_internal_compile_nocache.php | 73 + .../smarty_internal_compile_parent.php | 31 + ..._internal_compile_private_block_plugin.php | 124 + ...nternal_compile_private_foreachsection.php | 228 + ...ternal_compile_private_function_plugin.php | 78 + ...arty_internal_compile_private_modifier.php | 163 + ..._compile_private_object_block_function.php | 42 + ...ternal_compile_private_object_function.php | 85 + ...ernal_compile_private_print_expression.php | 161 + ...ernal_compile_private_registered_block.php | 72 + ...al_compile_private_registered_function.php | 91 + ...ernal_compile_private_special_variable.php | 130 + .../smarty_internal_compile_rdelim.php | 34 + .../smarty_internal_compile_section.php | 462 + .../smarty_internal_compile_setfilter.php | 68 + ...ty_internal_compile_shared_inheritance.php | 49 + .../smarty_internal_compile_while.php | 100 + .../smarty_internal_compilebase.php | 203 + .../smarty_internal_config_file_compiler.php | 211 + .../smarty_internal_configfilelexer.php | 739 ++ .../smarty_internal_configfileparser.php | 1046 ++ .../libs/sysplugins/smarty_internal_data.php | 272 + .../libs/sysplugins/smarty_internal_debug.php | 428 + .../smarty_internal_errorhandler.php | 114 + .../smarty_internal_extension_handler.php | 197 + ...rty_internal_method_addautoloadfilters.php | 53 + ...ty_internal_method_adddefaultmodifiers.php | 42 + .../smarty_internal_method_append.php | 74 + .../smarty_internal_method_appendbyref.php | 49 + .../smarty_internal_method_assignbyref.php | 36 + .../smarty_internal_method_assignglobal.php | 44 + .../smarty_internal_method_clearallassign.php | 36 + .../smarty_internal_method_clearallcache.php | 41 + .../smarty_internal_method_clearassign.php | 43 + .../smarty_internal_method_clearcache.php | 50 + ..._internal_method_clearcompiledtemplate.php | 131 + .../smarty_internal_method_clearconfig.php | 41 + ...marty_internal_method_compileallconfig.php | 36 + ...ty_internal_method_compilealltemplates.php | 130 + .../smarty_internal_method_configload.php | 182 + .../smarty_internal_method_createdata.php | 44 + ...rty_internal_method_getautoloadfilters.php | 37 + ...arty_internal_method_getconfigvariable.php | 34 + .../smarty_internal_method_getconfigvars.php | 58 + ...marty_internal_method_getdebugtemplate.php | 35 + ...ty_internal_method_getdefaultmodifiers.php | 35 + .../smarty_internal_method_getglobal.php | 47 + ...ty_internal_method_getregisteredobject.php | 44 + ...arty_internal_method_getstreamvariable.php | 50 + .../smarty_internal_method_gettags.php | 63 + ...smarty_internal_method_gettemplatevars.php | 119 + .../smarty_internal_method_literals.php | 100 + .../smarty_internal_method_loadfilter.php | 77 + .../smarty_internal_method_loadplugin.php | 111 + .../smarty_internal_method_mustcompile.php | 50 + ..._internal_method_registercacheresource.php | 42 + .../smarty_internal_method_registerclass.php | 46 + ...al_method_registerdefaultconfighandler.php | 42 + ...al_method_registerdefaultpluginhandler.php | 43 + ..._method_registerdefaulttemplatehandler.php | 88 + .../smarty_internal_method_registerfilter.php | 87 + .../smarty_internal_method_registerobject.php | 84 + .../smarty_internal_method_registerplugin.php | 58 + ...marty_internal_method_registerresource.php | 39 + ...rty_internal_method_setautoloadfilters.php | 72 + ...marty_internal_method_setdebugtemplate.php | 41 + ...ty_internal_method_setdefaultmodifiers.php | 38 + .../smarty_internal_method_unloadfilter.php | 43 + ...nternal_method_unregistercacheresource.php | 40 + ...marty_internal_method_unregisterfilter.php | 43 + ...marty_internal_method_unregisterobject.php | 40 + ...marty_internal_method_unregisterplugin.php | 41 + ...rty_internal_method_unregisterresource.php | 40 + .../smarty_internal_nocache_insert.php | 51 + .../sysplugins/smarty_internal_parsetree.php | 50 + .../smarty_internal_parsetree_code.php | 42 + .../smarty_internal_parsetree_dq.php | 95 + .../smarty_internal_parsetree_dqcontent.php | 42 + .../smarty_internal_parsetree_tag.php | 67 + .../smarty_internal_parsetree_template.php | 169 + .../smarty_internal_parsetree_text.php | 57 + .../smarty_internal_resource_eval.php | 94 + .../smarty_internal_resource_extends.php | 126 + .../smarty_internal_resource_file.php | 180 + .../smarty_internal_resource_php.php | 116 + .../smarty_internal_resource_stream.php | 78 + .../smarty_internal_resource_string.php | 108 + .../smarty_internal_runtime_cachemodify.php | 68 + ...rty_internal_runtime_cacheresourcefile.php | 139 + .../smarty_internal_runtime_capture.php | 174 + .../smarty_internal_runtime_codeframe.php | 103 + .../smarty_internal_runtime_filterhandler.php | 69 + .../smarty_internal_runtime_foreach.php | 162 + ...smarty_internal_runtime_getincludepath.php | 181 + .../smarty_internal_runtime_inheritance.php | 251 + .../smarty_internal_runtime_make_nocache.php | 54 + .../smarty_internal_runtime_tplfunction.php | 177 + .../smarty_internal_runtime_updatecache.php | 183 + .../smarty_internal_runtime_updatescope.php | 115 + .../smarty_internal_runtime_writefile.php | 91 + ...smarty_internal_smartytemplatecompiler.php | 184 + .../sysplugins/smarty_internal_template.php | 741 ++ .../smarty_internal_templatebase.php | 401 + .../smarty_internal_templatecompilerbase.php | 1774 ++++ .../smarty_internal_templatelexer.php | 1095 ++ .../smarty_internal_templateparser.php | 2929 ++++++ .../smarty_internal_testinstall.php | 605 ++ .../sysplugins/smarty_internal_undefined.php | 67 + .../libs/sysplugins/smarty_resource.php | 260 + .../sysplugins/smarty_resource_custom.php | 104 + .../sysplugins/smarty_resource_recompiled.php | 94 + .../sysplugins/smarty_resource_uncompiled.php | 49 + .../libs/sysplugins/smarty_security.php | 680 ++ .../sysplugins/smarty_template_cached.php | 257 + .../sysplugins/smarty_template_compiled.php | 257 + .../sysplugins/smarty_template_config.php | 100 + .../smarty_template_resource_base.php | 152 + .../sysplugins/smarty_template_source.php | 213 + .../sysplugins/smarty_undefined_variable.php | 33 + .../libs/sysplugins/smarty_variable.php | 48 + .../sysplugins/smartycompilerexception.php | 73 + .../libs/sysplugins/smartyexception.php | 19 + .../smarty4/vendor/smarty/smarty/mkdocs.yml | 125 + .../smarty/run-tests-for-all-php-versions.sh | 17 + .../smarty4/vendor/smarty/smarty/run-tests.sh | 10 + .../civicrm/packages/smarty5/Smarty.php | 79 + .../civicrm/packages/smarty5/composer.json | 5 + .../civicrm/packages/smarty5/composer.lock | 169 + .../packages/smarty5/vendor/autoload.php | 12 + .../smarty5/vendor/composer/ClassLoader.php | 572 ++ .../vendor/composer/InstalledVersions.php | 352 + .../packages/smarty5/vendor/composer/LICENSE | 21 + .../vendor/composer/autoload_classmap.php | 10 + .../vendor/composer/autoload_files.php | 11 + .../vendor/composer/autoload_namespaces.php | 9 + .../smarty5/vendor/composer/autoload_psr4.php | 11 + .../smarty5/vendor/composer/autoload_real.php | 57 + .../vendor/composer/autoload_static.php | 46 + .../smarty5/vendor/composer/installed.json | 162 + .../smarty5/vendor/composer/installed.php | 41 + .../vendor/composer/platform_check.php | 26 + .../smarty5/vendor/smarty/smarty/CHANGELOG.md | 3713 +++++++ .../vendor/smarty/smarty/CONTRIBUTING.md | 119 + .../smarty5/vendor/smarty/smarty/LICENSE | 179 + .../smarty5/vendor/smarty/smarty/Makefile | 19 + .../smarty5/vendor/smarty/smarty/README.md | 20 + .../smarty5/vendor/smarty/smarty/SECURITY.md | 20 + .../smarty5/vendor/smarty/smarty/TODO.txt | 32 + .../vendor/smarty/smarty/changelog/.gitkeep | 0 .../vendor/smarty/smarty/composer.json | 53 + .../smarty/smarty/demo/configs/test.conf | 5 + .../vendor/smarty/smarty/demo/index.php | 35 + .../smarty/smarty/demo/templates/footer.tpl | 2 + .../smarty/smarty/demo/templates/header.tpl | 5 + .../smarty/smarty/demo/templates/index.tpl | 85 + .../vendor/smarty/smarty/docs/_config.yml | 1 + .../vendor/smarty/smarty/docs/api/basics.md | 93 + .../smarty/smarty/docs/api/caching/basics.md | 184 + .../docs/api/caching/custom-storage-layers.md | 36 + .../caching/multiple-caches-per-template.md | 137 + .../smarty/smarty/docs/api/configuring.md | 193 + .../smarty/docs/api/extending/block-tags.md | 59 + .../smarty/docs/api/extending/extensions.md | 101 + .../smarty/docs/api/extending/introduction.md | 10 + .../smarty/docs/api/extending/modifiers.md | 27 + .../smarty/smarty/docs/api/extending/tags.md | 84 + .../smarty/docs/api/filters/output-filters.md | 35 + .../smarty/docs/api/filters/postfilters.md | 33 + .../smarty/docs/api/filters/prefilters.md | 26 + .../smarty/smarty/docs/api/inheritance.md | 130 + .../smarty/smarty/docs/api/rendering.md | 86 + .../smarty/smarty/docs/api/resources.md | 322 + .../vendor/smarty/smarty/docs/api/security.md | 119 + .../smarty/docs/api/variables/assigning.md | 139 + .../smarty/docs/api/variables/config-files.md | 88 + .../smarty/docs/api/variables/objects.md | 106 + .../api/variables/static-class-methods.md | 39 + .../smarty/docs/api/variables/streams.md | 16 + .../smarty/smarty/docs/appendixes/tips.md | 273 + .../smarty/docs/appendixes/troubleshooting.md | 104 + .../designers/chapter-debugging-console.md | 39 + .../smarty/docs/designers/config-files.md | 74 + .../designers/language-basic-syntax/index.md | 33 + .../language-escaping.md | 78 + .../language-syntax-attributes.md | 49 + .../language-syntax-comments.md | 69 + .../language-syntax-operators.md | 88 + .../language-syntax-quotes.md | 54 + .../language-syntax-tags.md | 39 + .../language-syntax-variables.md | 106 + .../designers/language-builtin-functions.md | 0 .../language-builtin-functions/index.md | 35 + .../language-function-append.md | 49 + .../language-function-assign.md | 148 + .../language-function-block.md | 201 + .../language-function-call.md | 77 + .../language-function-capture.md | 75 + .../language-function-config-load.md | 87 + .../language-function-debug.md | 17 + .../language-function-extends.md | 37 + .../language-function-for.md | 91 + .../language-function-foreach.md | 389 + .../language-function-function.md | 89 + .../language-function-if.md | 92 + .../language-function-include.md | 187 + .../language-function-insert.md | 0 .../language-function-ldelim.md | 51 + .../language-function-literal.md | 34 + .../language-function-nocache.md | 20 + .../language-function-section.md | 610 ++ .../language-function-setfilter.md | 43 + .../language-function-strip.md | 45 + .../language-function-while.md | 20 + .../designers/language-combining-modifiers.md | 32 + .../language-custom-functions/index.md | 19 + .../language-function-counter.md | 45 + .../language-function-cycle.md | 55 + .../language-function-debug.md | 14 + .../language-function-eval.md | 81 + .../language-function-fetch.md | 61 + .../language-function-html-checkboxes.md | 102 + .../language-function-html-image.md | 58 + .../language-function-html-options.md | 146 + .../language-function-html-radios.md | 104 + .../language-function-html-select-date.md | 118 + .../language-function-html-select-time.md | 100 + .../language-function-html-table.md | 93 + .../language-function-mailto.md | 61 + .../language-function-math.md | 99 + .../language-function-textformat.md | 182 + .../designers/language-modifiers/index.md | 104 + .../language-modifier-capitalize.md | 49 + .../language-modifier-cat.md | 36 + .../language-modifier-count-characters.md | 43 + .../language-modifier-count-paragraphs.md | 41 + .../language-modifier-count-sentences.md | 39 + .../language-modifier-count-words.md | 35 + .../language-modifier-count.md | 21 + .../language-modifier-date-format.md | 145 + .../language-modifier-debug-print-var.md | 26 + .../language-modifier-default.md | 45 + .../language-modifier-escape.md | 78 + .../language-modifier-from-charset.md | 20 + .../language-modifier-indent.md | 67 + .../language-modifier-is_array.md | 9 + .../language-modifier-isset.md | 11 + .../language-modifier-join.md | 26 + .../language-modifier-json-encode.md | 27 + .../language-modifier-lower.md | 34 + .../language-modifier-nl2br.md | 37 + .../language-modifier-noprint.md | 9 + .../language-modifier-number-format.md | 32 + .../language-modifier-regex-replace.md | 55 + .../language-modifier-replace.md | 45 + .../language-modifier-round.md | 35 + .../language-modifier-spacify.md | 44 + .../language-modifier-split.md | 32 + .../language-modifier-str-repeat.md | 14 + .../language-modifier-string-format.md | 43 + .../language-modifier-strip-tags.md | 46 + .../language-modifier-strip.md | 42 + .../language-modifier-strlen.md | 9 + .../language-modifier-substr.md | 25 + .../language-modifier-to-charset.md | 20 + .../language-modifier-truncate.md | 57 + .../language-modifier-unescape.md | 43 + .../language-modifier-upper.md | 33 + .../language-modifier-wordwrap.md | 73 + .../designers/language-variables/index.md | 36 + .../language-assigned-variables.md | 126 + .../language-config-variables.md | 79 + .../language-variable-scopes.md | 60 + .../language-variables-smarty.md | 155 + .../vendor/smarty/smarty/docs/features.md | 151 + .../smarty/smarty/docs/getting-started.md | 197 + .../vendor/smarty/smarty/docs/img/favicon.ico | Bin 0 -> 7782 bytes .../vendor/smarty/smarty/docs/index.md | 35 + .../vendor/smarty/smarty/docs/philosophy.md | 107 + .../api-functions/add-extension.md | 0 .../api-functions/api-add-plugins-dir.md | 42 + .../programmers/api-functions/api-append.md | 60 + .../programmers/api-functions/api-assign.md | 83 + .../api-functions/api-clear-all-assign.md | 34 + .../api-functions/api-clear-all-cache.md | 37 + .../api-functions/api-clear-assign.md | 32 + .../api-functions/api-clear-cache.md | 60 + .../api-functions/api-clear-compiled-tpl.md | 44 + .../api-functions/api-clear-config.md | 35 + .../api-functions/api-compile-all-config.md | 61 + .../api-compile-all-templates.md | 71 + .../api-functions/api-config-load.md | 47 + .../api-functions/api-create-data.md | 52 + .../api-functions/api-create-template.md | 99 + .../api-functions/api-disable-security.md | 15 + .../programmers/api-functions/api-display.md | 84 + .../api-functions/api-enable-security.md | 41 + .../programmers/api-functions/api-fetch.md | 91 + .../api-functions/api-get-config-dir.md | 40 + .../api-functions/api-get-config-vars.md | 37 + .../api-functions/api-get-plugins-dir.md | 31 + .../api-get-registered-object.md | 36 + .../api-functions/api-get-template-vars.md | 37 + .../api-functions/api-is-cached.md | 81 + .../api-functions/api-load-filter.md | 41 + .../api-functions/api-mute-expected-errors.md | 21 + .../api-register-cacheresource.md | 40 + .../api-functions/api-register-class.md | 68 + .../api-register-default-plugin-handler.md | 93 + .../api-functions/api-register-filter.md | 44 + .../api-functions/api-register-object.md | 44 + .../api-functions/api-register-plugin.md | 110 + .../api-functions/api-register-resource.md | 46 + .../api-functions/api-set-plugins-dir.md | 46 + .../api-functions/api-test-install.md | 22 + .../api-unregister-cacheresource.md | 28 + .../api-functions/api-unregister-filter.md | 23 + .../api-functions/api-unregister-object.md | 17 + .../api-functions/api-unregister-plugin.md | 36 + .../api-functions/api-unregister-resource.md | 28 + .../api-variables/variable-auto-literal.md | 17 + .../api-variables/variable-cache-dir.md | 35 + .../api-variables/variable-cache-id.md | 11 + .../api-variables/variable-cache-lifetime.md | 30 + .../api-variables/variable-cache-locking.md | 11 + .../variable-cache-modified-check.md | 11 + .../api-variables/variable-caching-type.md | 9 + .../api-variables/variable-caching.md | 38 + .../api-variables/variable-compile-dir.md | 29 + .../api-variables/variable-compile-id.md | 44 + .../api-variables/variable-compile-locking.md | 7 + .../api-variables/variable-compiler-class.md | 6 + .../variable-config-booleanize.md | 8 + .../api-variables/variable-config-dir.md | 23 + .../variable-config-overwrite.md | 40 + .../variable-config-read-hidden.md | 8 + .../api-variables/variable-debug-template.md | 8 + .../api-variables/variable-debugging-ctrl.md | 20 + .../api-variables/variable-debugging.md | 17 + .../variable-default-config-handler-func.md | 50 + .../variable-default-config-type.md | 7 + .../variable-default-modifiers.md | 8 + .../variable-default-resource-type.md | 7 + .../variable-default-template-handler-func.md | 50 + .../api-variables/variable-error-reporting.md | 17 + .../api-variables/variable-escape-html.md | 21 + .../api-variables/variable-force-cache.md | 6 + .../api-variables/variable-force-compile.md | 9 + .../api-variables/variable-locking-timeout.md | 7 + .../variable-merge-compiled-includes.md | 27 + .../api-variables/variable-smarty-debug-id.md | 9 + .../api-variables/variable-template-dir.md | 26 + .../variable-use-include-path.md | 0 .../api-variables/variable-use-sub-dirs.md | 31 + .../vendor/smarty/smarty/docs/upgrading.md | 157 + .../smarty/smarty/libs/Smarty.class.php | 42 + .../smarty5/vendor/smarty/smarty/mkdocs.yml | 156 + .../smarty/run-tests-for-all-php-versions.sh | 16 + .../smarty5/vendor/smarty/smarty/run-tests.sh | 10 + .../smarty/smarty/src/BlockHandler/Base.php | 19 + .../BlockHandler/BlockHandlerInterface.php | 10 + .../src/BlockHandler/BlockPluginWrapper.php | 19 + .../smarty/src/BlockHandler/TextFormat.php | 110 + .../smarty/smarty/src/Cacheresource/Base.php | 156 + .../smarty/src/Cacheresource/Custom.php | 303 + .../smarty/smarty/src/Cacheresource/File.php | 338 + .../src/Cacheresource/KeyValueStore.php | 541 + .../vendor/smarty/smarty/src/Compile/Base.php | 233 + .../smarty/src/Compile/BlockCompiler.php | 228 + .../smarty/src/Compile/CompilerInterface.php | 26 + .../Compile/DefaultHandlerBlockCompiler.php | 29 + .../DefaultHandlerFunctionCallCompiler.php | 47 + .../src/Compile/FunctionCallCompiler.php | 79 + .../src/Compile/Modifier/BCPluginWrapper.php | 19 + .../smarty/src/Compile/Modifier/Base.php | 49 + .../Compile/Modifier/CatModifierCompiler.php | 25 + .../CountCharactersModifierCompiler.php | 21 + .../CountParagraphsModifierCompiler.php | 19 + .../CountSentencesModifierCompiler.php | 19 + .../Modifier/CountWordsModifierCompiler.php | 20 + .../Modifier/DefaultModifierCompiler.php | 26 + .../Modifier/EmptyModifierCompiler.php | 19 + .../Modifier/EscapeModifierCompiler.php | 56 + .../Modifier/FromCharsetModifierCompiler.php | 21 + .../Modifier/IndentModifierCompiler.php | 24 + .../Modifier/IsArrayModifierCompiler.php | 19 + .../Modifier/IssetModifierCompiler.php | 25 + .../Modifier/JsonEncodeModifierCompiler.php | 14 + .../Modifier/LowerModifierCompiler.php | 19 + .../Modifier/ModifierCompilerInterface.php | 17 + .../Modifier/Nl2brModifierCompiler.php | 17 + .../Modifier/NoPrintModifierCompiler.php | 18 + .../Modifier/RoundModifierCompiler.php | 18 + .../Modifier/StrRepeatModifierCompiler.php | 17 + .../Modifier/StringFormatModifierCompiler.php | 18 + .../Modifier/StripModifierCompiler.php | 24 + .../Modifier/StripTagsModifierCompiler.php | 22 + .../Modifier/StrlenModifierCompiler.php | 18 + .../Modifier/SubstrModifierCompiler.php | 15 + .../Modifier/ToCharsetModifierCompiler.php | 21 + .../Modifier/UnescapeModifierCompiler.php | 34 + .../Modifier/UpperModifierCompiler.php | 18 + .../Modifier/WordWrapModifierCompiler.php | 27 + .../smarty/src/Compile/ModifierCompiler.php | 95 + .../src/Compile/ObjectMethodBlockCompiler.php | 44 + .../src/Compile/ObjectMethodCallCompiler.php | 76 + .../src/Compile/PrintExpressionCompiler.php | 95 + .../src/Compile/SpecialVariableCompiler.php | 134 + .../smarty/smarty/src/Compile/Tag/Append.php | 58 + .../smarty/smarty/src/Compile/Tag/Assign.php | 95 + .../src/Compile/Tag/BCPluginWrapper.php | 31 + .../smarty/smarty/src/Compile/Tag/Block.php | 92 + .../smarty/src/Compile/Tag/BlockClose.php | 110 + .../smarty/src/Compile/Tag/BreakTag.php | 123 + .../smarty/smarty/src/Compile/Tag/Call.php | 81 + .../smarty/smarty/src/Compile/Tag/Capture.php | 72 + .../smarty/src/Compile/Tag/CaptureClose.php | 43 + .../smarty/src/Compile/Tag/ConfigLoad.php | 77 + .../smarty/src/Compile/Tag/ContinueTag.php | 27 + .../smarty/smarty/src/Compile/Tag/Debug.php | 45 + .../smarty/src/Compile/Tag/ElseIfTag.php | 86 + .../smarty/smarty/src/Compile/Tag/ElseTag.php | 29 + .../smarty/smarty/src/Compile/Tag/EvalTag.php | 74 + .../smarty/src/Compile/Tag/ExtendsTag.php | 87 + .../smarty/src/Compile/Tag/ForClose.php | 51 + .../smarty/smarty/src/Compile/Tag/ForElse.php | 30 + .../smarty/smarty/src/Compile/Tag/ForTag.php | 101 + .../smarty/src/Compile/Tag/ForeachClose.php | 55 + .../smarty/src/Compile/Tag/ForeachElse.php | 35 + .../smarty/src/Compile/Tag/ForeachSection.php | 206 + .../smarty/src/Compile/Tag/ForeachTag.php | 286 + .../smarty/src/Compile/Tag/FunctionClose.php | 164 + .../smarty/src/Compile/Tag/FunctionTag.php | 73 + .../smarty/smarty/src/Compile/Tag/IfClose.php | 48 + .../smarty/smarty/src/Compile/Tag/IfTag.php | 70 + .../smarty/src/Compile/Tag/IncludeTag.php | 189 + .../smarty/src/Compile/Tag/Inheritance.php | 54 + .../smarty/smarty/src/Compile/Tag/Ldelim.php | 41 + .../smarty/smarty/src/Compile/Tag/Nocache.php | 36 + .../smarty/src/Compile/Tag/NocacheClose.php | 37 + .../smarty/smarty/src/Compile/Tag/Rdelim.php | 36 + .../smarty/smarty/src/Compile/Tag/Section.php | 399 + .../smarty/src/Compile/Tag/SectionClose.php | 48 + .../smarty/src/Compile/Tag/SectionElse.php | 29 + .../smarty/src/Compile/Tag/Setfilter.php | 40 + .../smarty/src/Compile/Tag/SetfilterClose.php | 43 + .../smarty/src/Compile/Tag/WhileClose.php | 45 + .../smarty/src/Compile/Tag/WhileTag.php | 72 + .../smarty/src/Compiler/BaseCompiler.php | 23 + .../smarty/smarty/src/Compiler/CodeFrame.php | 126 + .../smarty/smarty/src/Compiler/Configfile.php | 173 + .../smarty/smarty/src/Compiler/Template.php | 1489 +++ .../smarty/smarty/src/CompilerException.php | 73 + .../smarty5/vendor/smarty/smarty/src/Data.php | 521 + .../vendor/smarty/smarty/src/Debug.php | 370 + .../vendor/smarty/smarty/src/ErrorHandler.php | 97 + .../vendor/smarty/smarty/src/Exception.php | 16 + .../smarty/src/Extension/BCPluginsAdapter.php | 229 + .../smarty/smarty/src/Extension/Base.php | 41 + .../smarty/src/Extension/CallbackWrapper.php | 35 + .../smarty/src/Extension/CoreExtension.php | 49 + .../smarty/src/Extension/DefaultExtension.php | 756 ++ .../src/Extension/ExtensionInterface.php | 83 + .../smarty/src/Filter/FilterInterface.php | 9 + .../smarty/src/Filter/FilterPluginWrapper.php | 15 + .../src/Filter/Output/TrimWhitespace.php | 91 + .../src/FunctionHandler/BCPluginWrapper.php | 21 + .../smarty/src/FunctionHandler/Base.php | 21 + .../smarty/src/FunctionHandler/Count.php | 36 + .../smarty/src/FunctionHandler/Counter.php | 61 + .../smarty/src/FunctionHandler/Cycle.php | 90 + .../smarty/src/FunctionHandler/Fetch.php | 203 + .../FunctionHandlerInterface.php | 10 + .../smarty/src/FunctionHandler/HtmlBase.php | 107 + .../src/FunctionHandler/HtmlCheckboxes.php | 189 + .../smarty/src/FunctionHandler/HtmlImage.php | 149 + .../src/FunctionHandler/HtmlOptions.php | 223 + .../smarty/src/FunctionHandler/HtmlRadios.php | 174 + .../src/FunctionHandler/HtmlSelectDate.php | 381 + .../src/FunctionHandler/HtmlSelectTime.php | 334 + .../smarty/src/FunctionHandler/HtmlTable.php | 161 + .../smarty/src/FunctionHandler/Mailto.php | 141 + .../smarty/src/FunctionHandler/Math.php | 140 + .../smarty/src/Lexer/ConfigfileLexer.php | 707 ++ .../smarty/src/Lexer/ConfigfileLexer.plex | 321 + .../smarty/smarty/src/Lexer/TemplateLexer.php | 1083 ++ .../smarty/src/Lexer/TemplateLexer.plex | 677 ++ .../smarty/smarty/src/ParseTree/Base.php | 45 + .../smarty/smarty/src/ParseTree/Code.php | 45 + .../vendor/smarty/smarty/src/ParseTree/Dq.php | 97 + .../smarty/smarty/src/ParseTree/DqContent.php | 44 + .../smarty/smarty/src/ParseTree/Tag.php | 70 + .../smarty/smarty/src/ParseTree/Template.php | 172 + .../smarty/smarty/src/ParseTree/Text.php | 59 + .../smarty/src/Parser/ConfigfileParser.php | 972 ++ .../smarty/src/Parser/ConfigfileParser.y | 352 + .../smarty/src/Parser/TemplateParser.php | 3051 ++++++ .../smarty/smarty/src/Parser/TemplateParser.y | 1312 +++ .../smarty/smarty/src/Resource/BasePlugin.php | 145 + .../smarty/src/Resource/CustomPlugin.php | 105 + .../smarty/src/Resource/ExtendsPlugin.php | 112 + .../smarty/smarty/src/Resource/FilePlugin.php | 180 + .../smarty/src/Resource/RecompiledPlugin.php | 50 + .../smarty/src/Resource/StreamPlugin.php | 71 + .../smarty/smarty/src/Resource/StringEval.php | 85 + .../smarty/src/Resource/StringPlugin.php | 94 + .../smarty/smarty/src/Runtime/Block.php | 92 + .../smarty/src/Runtime/CaptureRuntime.php | 163 + .../Runtime/DefaultPluginHandlerRuntime.php | 73 + .../smarty/src/Runtime/ForeachRuntime.php | 160 + .../smarty/src/Runtime/InheritanceRuntime.php | 243 + .../smarty/src/Runtime/TplFunctionRuntime.php | 144 + .../vendor/smarty/smarty/src/Security.php | 560 + .../vendor/smarty/smarty/src/Smarty.php | 2224 ++++ .../vendor/smarty/smarty/src/Template.php | 732 ++ .../smarty/smarty/src/Template/Cached.php | 428 + .../smarty/smarty/src/Template/Compiled.php | 302 + .../smarty/smarty/src/Template/Config.php | 36 + .../smarty/src/Template/GeneratedPhpFile.php | 159 + .../smarty/smarty/src/Template/Source.php | 285 + .../vendor/smarty/smarty/src/TemplateBase.php | 425 + .../vendor/smarty/smarty/src/TestInstall.php | 211 + .../smarty/smarty/src/UndefinedVariable.php | 19 + .../vendor/smarty/smarty/src/Variable.php | 118 + .../vendor/smarty/smarty/src/debug.tpl | 173 + .../vendor/smarty/smarty/src/functions.php | 253 + .../vendor/symfony/polyfill-mbstring/LICENSE | 19 + .../symfony/polyfill-mbstring/Mbstring.php | 947 ++ .../symfony/polyfill-mbstring/README.md | 13 + .../Resources/unidata/caseFolding.php | 119 + .../Resources/unidata/lowerCase.php | 1397 +++ .../Resources/unidata/titleCaseRegexp.php | 5 + .../Resources/unidata/upperCase.php | 1489 +++ .../symfony/polyfill-mbstring/bootstrap.php | 151 + .../symfony/polyfill-mbstring/bootstrap80.php | 147 + .../symfony/polyfill-mbstring/composer.json | 38 + www/modules/civicrm/release-notes.md | 170 +- www/modules/civicrm/release-notes/5.70.1.md | 48 - www/modules/civicrm/release-notes/5.71.0.md | 760 ++ www/modules/civicrm/release-notes/5.71.1.md | 39 + www/modules/civicrm/release-notes/5.71.2.md | 41 + www/modules/civicrm/release-notes/5.72.0.md | 618 ++ www/modules/civicrm/release-notes/5.72.1.md | 42 + www/modules/civicrm/release-notes/5.72.2.md | 45 + .../release-notes/{5.70.2.md => 5.72.3.md} | 10 +- www/modules/civicrm/release-notes/5.73.0.md | 600 ++ www/modules/civicrm/release-notes/5.73.1.md | 40 + www/modules/civicrm/release-notes/5.73.2.md | 42 + www/modules/civicrm/release-notes/5.73.3.md | 44 + www/modules/civicrm/release-notes/5.73.4.md | 43 + www/modules/civicrm/release-notes/5.74.0.md | 684 ++ www/modules/civicrm/release-notes/5.74.1.md | 41 + www/modules/civicrm/release-notes/5.74.2.md | 43 + www/modules/civicrm/release-notes/5.74.3.md | 41 + www/modules/civicrm/release-notes/5.74.4.md | 47 + .../civicrm/schema/ACL/ACL.entityType.php | 163 + .../schema/ACL/ACLCache.entityType.php | 83 + .../schema/ACL/ACLEntityRole.entityType.php | 95 + .../schema/Activity/Activity.entityType.php | 456 + .../Activity/ActivityContact.entityType.php | 94 + .../civicrm/schema/Batch/Batch.entityType.php | 198 + .../schema/Batch/EntityBatch.entityType.php | 89 + .../schema/Campaign/Campaign.entityType.php | 302 + .../Campaign/CampaignGroup.entityType.php | 83 + .../schema/Campaign/Survey.entityType.php | 255 + .../civicrm/schema/Case/Case.entityType.php | 200 + .../schema/Case/CaseActivity.entityType.php | 67 + .../schema/Case/CaseContact.entityType.php | 68 + .../schema/Case/CaseType.entityType.php | 106 + .../Contact/ACLContactCache.entityType.php | 68 + .../schema/Contact/Contact.entityType.php | 992 ++ .../schema/Contact/ContactType.entityType.php | 139 + .../Contact/DashboardContact.entityType.php | 97 + .../schema/Contact/Group.entityType.php | 326 + .../Contact/GroupContact.entityType.php | 115 + .../Contact/GroupContactCache.entityType.php | 72 + .../Contact/GroupNesting.entityType.php | 58 + .../Contact/GroupOrganization.entityType.php | 73 + .../Contact/Relationship.entityType.php | 199 + .../Contact/RelationshipCache.entityType.php | 231 + .../Contact/RelationshipType.entityType.php | 188 + .../schema/Contact/SavedSearch.entityType.php | 190 + .../SubscriptionHistory.entityType.php | 111 + .../Contribute/Contribution.entityType.php | 606 ++ .../ContributionPage.entityType.php | 538 + .../ContributionProduct.entityType.php | 136 + .../ContributionRecur.entityType.php | 419 + .../ContributionSoft.entityType.php | 169 + .../schema/Contribute/Premium.entityType.php | 125 + .../Contribute/PremiumsProduct.entityType.php | 84 + .../schema/Contribute/Product.entityType.php | 235 + .../schema/Contribute/Widget.entityType.php | 181 + .../schema/Core/ActionLog.entityType.php | 115 + .../schema/Core/ActionSchedule.entityType.php | 577 ++ .../schema/Core/Address.entityType.php | 478 + .../schema/Core/AddressFormat.entityType.php | 32 + .../civicrm/schema/Core/Cache.entityType.php | 104 + .../schema/Core/Component.entityType.php | 46 + .../schema/Core/Country.entityType.php | 148 + .../civicrm/schema/Core/County.entityType.php | 94 + .../schema/Core/CustomField.entityType.php | 371 + .../schema/Core/CustomGroup.entityType.php | 289 + .../schema/Core/Dashboard.entityType.php | 150 + .../schema/Core/Discount.entityType.php | 109 + .../civicrm/schema/Core/Domain.entityType.php | 94 + .../civicrm/schema/Core/Email.entityType.php | 197 + .../schema/Core/EntityFile.entityType.php | 78 + .../schema/Core/EntityTag.entityType.php | 86 + .../schema/Core/Extension.entityType.php | 136 + .../civicrm/schema/Core/File.entityType.php | 95 + .../civicrm/schema/Core/IM.entityType.php | 124 + .../civicrm/schema/Core/Job.entityType.php | 164 + .../civicrm/schema/Core/JobLog.entityType.php | 102 + .../schema/Core/LocBlock.entityType.php | 139 + .../schema/Core/LocationType.entityType.php | 121 + .../civicrm/schema/Core/Log.entityType.php | 86 + .../schema/Core/MailSettings.entityType.php | 278 + .../schema/Core/Managed.entityType.php | 108 + .../schema/Core/Mapping.entityType.php | 71 + .../schema/Core/MappingField.entityType.php | 169 + .../civicrm/schema/Core/Menu.entityType.php | 244 + .../Core/MessageTemplate.entityType.php | 140 + .../schema/Core/Navigation.entityType.php | 163 + .../civicrm/schema/Core/Note.entityType.php | 166 + .../civicrm/schema/Core/OpenID.entityType.php | 103 + .../schema/Core/OptionGroup.entityType.php | 133 + .../schema/Core/OptionValue.entityType.php | 255 + .../civicrm/schema/Core/Phone.entityType.php | 176 + .../Core/PreferencesDate.entityType.php | 101 + .../schema/Core/PrevNextCache.entityType.php | 86 + .../schema/Core/PrintLabel.entityType.php | 136 + .../Core/RecurringEntity.entityType.php | 69 + .../schema/Core/Setting.entityType.php | 142 + .../schema/Core/StateProvince.entityType.php | 88 + .../Core/StatusPreference.entityType.php | 130 + .../schema/Core/SystemLog.entityType.php | 82 + .../civicrm/schema/Core/Tag.entityType.php | 168 + .../schema/Core/Timezone.entityType.php | 76 + .../schema/Core/Translation.entityType.php | 118 + .../schema/Core/UFField.entityType.php | 238 + .../schema/Core/UFGroup.entityType.php | 326 + .../civicrm/schema/Core/UFJoin.entityType.php | 119 + .../schema/Core/UFMatch.entityType.php | 120 + .../schema/Core/UserJob.entityType.php | 174 + .../schema/Core/Website.entityType.php | 76 + .../Core/WordReplacement.entityType.php | 101 + .../schema/Core/WorldRegion.entityType.php | 39 + .../civicrm/schema/Cxn/Cxn.entityType.php | 145 + .../Dedupe/DedupeException.entityType.php | 67 + .../schema/Dedupe/DedupeRule.entityType.php | 77 + .../Dedupe/DedupeRuleGroup.entityType.php | 101 + .../civicrm/schema/Event/Cart.entityType.php | 48 + .../civicrm/schema/Event/Event.entityType.php | 863 ++ .../schema/Event/EventInCart.entityType.php | 55 + .../schema/Event/Participant.entityType.php | 391 + .../Event/ParticipantPayment.entityType.php | 68 + .../ParticipantStatusType.entityType.php | 119 + .../schema/Financial/Currency.entityType.php | 76 + .../EntityFinancialAccount.entityType.php | 109 + .../EntityFinancialTrxn.entityType.php | 96 + .../Financial/FinancialAccount.entityType.php | 200 + .../Financial/FinancialItem.entityType.php | 180 + .../Financial/FinancialTrxn.entityType.php | 267 + .../Financial/FinancialType.entityType.php | 106 + .../Financial/PaymentProcessor.entityType.php | 291 + .../PaymentProcessorType.entityType.php | 255 + .../Financial/PaymentToken.entityType.php | 158 + .../schema/Friend/Friend.entityType.php | 119 + .../Mailing/BouncePattern.entityType.php | 51 + .../schema/Mailing/BounceType.entityType.php | 48 + .../schema/Mailing/Mailing.entityType.php | 566 ++ .../schema/Mailing/MailingAB.entityType.php | 147 + .../Mailing/MailingComponent.entityType.php | 100 + .../Mailing/MailingEventBounce.entityType.php | 71 + .../MailingEventConfirm.entityType.php | 48 + .../MailingEventDelivered.entityType.php | 48 + .../MailingEventForward.entityType.php | 62 + .../Mailing/MailingEventOpened.entityType.php | 48 + .../Mailing/MailingEventQueue.entityType.php | 126 + .../Mailing/MailingEventReply.entityType.php | 48 + .../MailingEventSubscribe.entityType.php | 78 + ...ailingEventTrackableURLOpen.entityType.php | 66 + .../MailingEventUnsubscribe.entityType.php | 55 + .../Mailing/MailingGroup.entityType.php | 85 + .../schema/Mailing/MailingJob.entityType.php | 143 + .../Mailing/MailingRecipients.entityType.php | 82 + .../MailingTrackableURL.entityType.php | 44 + .../schema/Mailing/Spool.entityType.php | 69 + .../schema/Member/Membership.entityType.php | 318 + .../Member/MembershipBlock.entityType.php | 153 + .../Member/MembershipLog.entityType.php | 116 + .../Member/MembershipPayment.entityType.php | 66 + .../Member/MembershipStatus.entityType.php | 193 + .../Member/MembershipType.entityType.php | 289 + .../civicrm/schema/PCP/PCP.entityType.php | 189 + .../schema/PCP/PCPBlock.entityType.php | 159 + .../schema/Pledge/Pledge.entityType.php | 357 + .../schema/Pledge/PledgeBlock.entityType.php | 129 + .../Pledge/PledgePayment.entityType.php | 181 + .../schema/Price/LineItem.entityType.php | 222 + .../schema/Price/PriceField.entityType.php | 220 + .../Price/PriceFieldValue.entityType.php | 246 + .../schema/Price/PriceSet.entityType.php | 196 + .../Price/PriceSetEntity.entityType.php | 79 + .../civicrm/schema/Queue/Queue.entityType.php | 141 + .../schema/Queue/QueueItem.entityType.php | 84 + .../Report/ReportInstance.entityType.php | 272 + .../schema/SMS/SmsProvider.entityType.php | 138 + .../setup/plugins/blocks/admin.civi-setup.php | 3 + .../setup/plugins/blocks/admin.tpl.php | 33 +- .../setup/plugins/blocks/database.tpl.php | 23 +- .../setup/plugins/init/Drupal.civi-setup.php | 2 +- .../init/StandaloneUsers.civi-setup.php | 13 +- .../BootstrapCivi.civi-setup.php | 2 +- .../InstallSchema.civi-setup.php | 17 +- .../installDatabase/Preboot.civi-setup.php | 2 +- .../StandaloneCivicrmFilesPath.civi-setup.php | 54 + www/modules/civicrm/setup/res/index.php.txt | 3 + www/modules/civicrm/setup/res/template.css | 39 +- .../setup/src/Setup/SchemaGenerator.php | 20 - .../civicrm/setup/src/Setup/SmartyUtil.php | 30 +- .../setup/src/Setup/UI/SetupController.php | 6 +- www/modules/civicrm/sql/civicrm.mysql | 6099 +++++------ www/modules/civicrm/sql/civicrm_data.mysql | 1851 ++-- www/modules/civicrm/sql/civicrm_drop.mysql | 281 +- .../civicrm/sql/civicrm_generated.mysql | 9001 +++++++++-------- .../civicrm/sql/civicrm_navigation.mysql | 6 +- www/modules/civicrm/sql/civicrm_sample.mysql | 2 + .../sql/civicrm_sample_custom_data.mysql | 7 +- .../templates/CRM/Activity/Form/Activity.tpl | 4 +- .../templates/CRM/Activity/Form/FollowUp.tpl | 10 +- .../templates/CRM/Activity/Form/Search.tpl | 5 +- .../CRM/Activity/Selector/Selector.tpl | 14 +- .../CRM/Admin/Form/MessageTemplates.tpl | 30 +- .../templates/CRM/Admin/Form/Options.tpl | 7 +- .../CRM/Admin/Form/Preferences/Address.hlp | 2 +- .../CRM/Admin/Form/Preferences/Address.tpl | 2 +- .../CRM/Admin/Form/ScheduleReminders.tpl | 19 +- .../templates/CRM/Admin/Form/Setting/Url.tpl | 3 +- .../templates/CRM/Admin/Page/APIExplorer.tpl | 18 +- .../CRM/Admin/Page/Extensions/Main.tpl | 2 +- .../CRM/Admin/Page/MessageTemplates.tpl | 6 +- .../templates/CRM/Batch/Form/Entry.tpl | 2 +- .../templates/CRM/Block/FullTextSearch.tpl | 2 +- .../templates/CRM/Campaign/Form/Campaign.tpl | 2 +- .../templates/CRM/Campaign/Form/Gotv.tpl | 2 +- .../templates/CRM/Campaign/Form/Petition.tpl | 2 +- .../CRM/Campaign/Form/Search/Common.tpl | 8 +- .../CRM/Campaign/Form/Survey/Main.tpl | 2 +- .../CRM/Campaign/Form/Survey/Results.tpl | 6 +- .../CRM/Campaign/Form/Task/Reserve.tpl | 14 +- .../templates/CRM/Case/Form/Activity.tpl | 36 +- .../templates/CRM/Case/Form/ActivityTab.tpl | 115 +- .../civicrm/templates/CRM/Case/Form/Case.tpl | 12 +- .../templates/CRM/Case/Form/CaseFilter.tpl | 10 +- .../templates/CRM/Case/Form/CaseView.js | 15 +- .../templates/CRM/Case/Form/CaseView.tpl | 32 +- .../templates/CRM/Case/Form/Search.tpl | 10 +- .../CRM/Case/Page/CustomDataView.tpl | 8 +- .../templates/CRM/Contact/Form/Contact.tpl | 45 +- .../templates/CRM/Contact/Form/CustomData.tpl | 3 +- .../CRM/Contact/Form/Edit/Address.tpl | 10 +- .../Contact/Form/Edit/Address/CustomData.tpl | 14 +- .../Form/Edit/CommunicationPreferences.tpl | 10 +- .../CRM/Contact/Form/Edit/CustomData.tpl | 17 +- .../CRM/Contact/Form/Edit/Demographics.tpl | 10 +- .../templates/CRM/Contact/Form/Edit/Email.tpl | 8 +- .../templates/CRM/Contact/Form/Edit/Notes.tpl | 10 +- .../CRM/Contact/Form/Edit/TagsAndGroups.tpl | 6 +- .../CRM/Contact/Form/Relationship.tpl | 6 +- .../CRM/Contact/Form/Search/Advanced.tpl | 4 +- .../Contact/Form/Search/AdvancedCriteria.tpl | 24 +- .../CRM/Contact/Form/Search/BasicCriteria.tpl | 10 +- .../CRM/Contact/Form/Search/Builder.tpl | 10 +- .../CRM/Contact/Form/Task/AddToGroup.tpl | 3 +- .../templates/CRM/Contact/Form/Task/Email.tpl | 2 +- .../CRM/Contact/Form/Task/EmailCommon.tpl | 43 +- .../CRM/Contact/Form/Task/PDFLetterCommon.tpl | 34 +- .../templates/CRM/Contact/Form/Task/SMS.tpl | 32 +- .../CRM/Contact/Form/Task/SMSCommon.tpl | 28 +- .../CRM/Contact/Form/Task/SaveSearch.tpl | 2 +- .../CRM/Contact/Import/Form/Preview.tpl | 54 +- .../CRM/Contact/Page/DedupeException.tpl | 41 +- .../templates/CRM/Contact/Page/DedupeFind.tpl | 10 +- .../CRM/Contact/Page/Inline/Address.tpl | 32 +- .../CRM/Contact/Page/Inline/Email.tpl | 2 +- .../CRM/Contact/Page/View/CustomDataView.tpl | 4 +- .../CRM/Contact/Page/View/GroupContact.tpl | 15 +- .../templates/CRM/Contact/Page/View/Log.tpl | 4 +- .../CRM/Contribute/Form/AdditionalPayment.tpl | 14 +- .../CRM/Contribute/Form/Contribution.tpl | 45 +- .../Form/ContributionPage/Premium.tpl | 12 +- .../Form/ContributionPage/Widget.tpl | 10 +- .../CRM/Contribute/Form/ContributionView.tpl | 26 +- .../CRM/Contribute/Form/ManagePremiums.tpl | 10 +- .../templates/CRM/Contribute/Form/Search.tpl | 10 +- .../Form/Search/ContributionRecur.tpl | 10 +- .../CRM/Contribute/Form/Task/Invoice.tpl | 2 +- .../CRM/Contribute/Form/Task/PDF.tpl | 2 +- .../CRM/Contribute/Form/Task/PDFLetter.tpl | 12 +- .../Contribute/Form/UpdateSubscription.tpl | 18 +- .../Page/ContributionRecurPayments.tpl | 6 +- .../templates/CRM/Core/Calendar/ICal.tpl | 3 - .../CRM/Core/Form/RecurringEntity.hlp | 63 - .../CRM/Core/Form/RecurringEntity.tpl | 22 +- .../templates/CRM/Custom/Form/CustomData.tpl | 8 +- .../CRM/Custom/Form/Edit/CustomData.tpl | 2 +- .../CRM/Custom/Form/Edit/CustomField.tpl | 2 +- .../templates/CRM/Custom/Form/Field.hlp | 2 +- .../templates/CRM/Custom/Form/Field.tpl | 5 + .../templates/CRM/Custom/Form/Search.tpl | 10 +- .../CRM/Custom/Page/CustomDataView.tpl | 8 +- .../templates/CRM/Dashlet/Page/Blog.tpl | 8 +- .../templates/CRM/Event/Form/EventFees.tpl | 4 +- .../CRM/Event/Form/ManageEvent/EventInfo.tpl | 58 +- .../Event/Form/ManageEvent/Registration.tpl | 71 +- .../CRM/Event/Form/ManageEvent/Repeat.tpl | 6 +- .../templates/CRM/Event/Form/Participant.tpl | 12 +- .../Event/Form/ParticipantFeeSelection.tpl | 2 +- .../CRM/Event/Form/ParticipantView.tpl | 38 +- .../Registration/AdditionalParticipant.tpl | 2 +- .../CRM/Event/Form/Registration/Confirm.tpl | 19 +- .../Form/Registration/DisplayProfile.tpl | 2 +- .../Form/Registration/EventInfoBlock.tpl | 8 +- .../CRM/Event/Form/Registration/Register.tpl | 6 +- .../CRM/Event/Form/Registration/ThankYou.tpl | 16 +- .../templates/CRM/Event/Form/Search.tpl | 8 +- .../templates/CRM/Event/Form/SearchEvent.tpl | 8 +- .../templates/CRM/Event/Form/Selector.tpl | 60 +- .../templates/CRM/Event/Page/EventInfo.tpl | 31 +- .../CRM/Financial/Form/BatchTransaction.tpl | 10 +- .../CRM/Financial/Form/FinancialAccount.tpl | 6 +- .../CRM/Financial/Form/FinancialBatch.tpl | 8 +- .../templates/CRM/Financial/Form/Search.tpl | 8 +- .../civicrm/templates/CRM/Form/attachment.tpl | 10 +- .../templates/CRM/Group/Form/Delete.tpl | 13 + .../civicrm/templates/CRM/Group/Form/Edit.tpl | 2 +- .../templates/CRM/Group/Form/Search.tpl | 8 +- .../templates/CRM/Mailing/Form/Approve.tpl | 10 +- .../CRM/Mailing/Form/InsertTokens.tpl | 41 +- .../templates/CRM/Mailing/Page/Report.tpl | 2 +- .../templates/CRM/Member/Form/Membership.tpl | 22 +- .../CRM/Member/Form/MembershipCommon.tpl | 2 +- .../CRM/Member/Form/MembershipRenewal.tpl | 6 +- .../CRM/Member/Form/MembershipView.tpl | 14 +- .../templates/CRM/Member/Form/Search.tpl | 10 +- .../CRM/Member/Page/MembershipType.tpl | 4 +- .../templates/CRM/Pledge/Form/Pledge.tpl | 18 +- .../templates/CRM/Pledge/Form/PledgeView.tpl | 18 +- .../templates/CRM/Pledge/Form/Search.tpl | 8 +- .../templates/CRM/Price/Form/Calculate.tpl | 2 +- .../templates/CRM/Price/Page/LineItem.tpl | 26 +- .../templates/CRM/Profile/Form/Search.tpl | 16 +- .../CRM/Report/Form/Tabs/FieldSelection.tpl | 10 +- .../CRM/Report/Form/Tabs/Filters.tpl | 10 +- .../CRM/Report/Page/InstanceList.tpl | 10 +- .../CRM/Report/Page/TemplateList.tpl | 10 +- .../civicrm/templates/CRM/SMS/Form/Group.tpl | 10 +- .../templates/CRM/SMS/Form/Schedule.tpl | 10 +- .../civicrm/templates/CRM/SMS/Form/Upload.tpl | 4 +- .../templates/CRM/Tag/Form/Tagtree.tpl | 2 +- .../templates/CRM/UF/Form/AdvanceSetting.tpl | 10 +- .../civicrm/templates/CRM/UF/Form/Field.hlp | 4 +- .../civicrm/templates/CRM/UF/Form/Field.tpl | 68 +- .../civicrm/templates/CRM/UF/Form/Fields.tpl | 8 +- .../templates/CRM/common/additionalBlocks.tpl | 2 +- .../CRM/common/civicrm.settings.php.template | 10 +- .../templates/CRM/common/customData.tpl | 6 +- .../templates/CRM/common/customDataBlock.tpl | 21 +- .../civicrm/templates/CRM/common/fatal.tpl | 25 +- .../templates/CRM/common/highLightImport.tpl | 5 +- .../templates/CRM/common/jsortable.tpl | 81 +- .../civicrm/templates/CRM/common/l10n.js.tpl | 3 +- .../templates/CRM/common/standalone.tpl | 2 +- www/modules/civicrm/vendor/autoload.php | 2 +- .../composer-compile-plugin/.travis.yml | 4 + .../src/Handler/ExportHandler.php | 6 + .../.github/workflows/main.yml | 4 +- .../composer-downloads-plugin/README.md | 13 +- .../composer-downloads-plugin/composer.json | 10 +- .../civicrm/vendor/civicrm/gitignore/LICENSE | 20 + .../{togos => civicrm}/gitignore/Makefile | 3 + .../vendor/civicrm/gitignore/README.md | 38 + .../gitignore/composer.json | 7 +- .../vendor/civicrm/gitignore/shell.nix | 106 + .../main/php/TOGoS/GitIgnore/FileFinder.php | 7 +- .../src/main/php/TOGoS/GitIgnore/Pattern.php | 0 .../src/main/php/TOGoS/GitIgnore/Rule.php | 16 +- .../src/main/php/TOGoS/GitIgnore/Ruleset.php | 0 .../src/main/php/TOGoS/GitIgnore/Util.php | 0 .../vendor/composer/autoload_files.php | 1 + .../vendor/composer/autoload_namespaces.php | 2 +- .../civicrm/vendor/composer/autoload_real.php | 14 +- .../vendor/composer/autoload_static.php | 15 +- .../civicrm/vendor/composer/include_paths.php | 2 +- .../civicrm/vendor/composer/installed.json | 365 +- .../civicrm/vendor/composer/installed.php | 120 +- .../civicrm/vendor/pear/auth_sasl/.travis.yml | 32 +- .../vendor/pear/auth_sasl/Auth/SASL.php | 22 +- .../pear/auth_sasl/Auth/SASL/CramMD5.php | 1 + .../pear/auth_sasl/Auth/SASL/DigestMD5.php | 1 + .../vendor/pear/auth_sasl/Auth/SASL/Login.php | 1 + .../vendor/pear/auth_sasl/Auth/SASL/SCRAM.php | 36 +- .../civicrm/vendor/pear/auth_sasl/README.md | 11 +- .../vendor/pear/auth_sasl/composer.json | 6 +- .../civicrm/vendor/pear/auth_sasl/package.xml | 44 +- .../vendor/pear/auth_sasl/phpunit.xml.dist | 2 +- www/modules/civicrm/vendor/pear/db/DB.php | 8 +- .../civicrm/vendor/pear/db/DB/common.php | 24 + .../civicrm/vendor/pear/db/DB/dbase.php | 2 +- .../civicrm/vendor/pear/db/DB/ibase.php | 6 +- .../civicrm/vendor/pear/db/DB/msql.php | 4 +- .../civicrm/vendor/pear/db/DB/mssql.php | 2 + .../civicrm/vendor/pear/db/DB/mysql.php | 43 +- .../civicrm/vendor/pear/db/DB/mysqli.php | 35 +- .../civicrm/vendor/pear/db/DB/oci8.php | 84 +- .../civicrm/vendor/pear/db/DB/odbc.php | 8 +- .../civicrm/vendor/pear/db/DB/pgsql.php | 28 +- .../civicrm/vendor/pear/db/DB/sqlite.php | 2 +- .../civicrm/vendor/pear/db/DB/sqlite3.php | 964 ++ .../civicrm/vendor/pear/db/DB/sybase.php | 6 +- .../civicrm/vendor/pear/db/PATCHES.txt | 8 - .../civicrm/vendor/pear/db/composer.json | 5 + .../civicrm/vendor/pear/mail/Mail/RFC822.php | 23 +- .../civicrm/vendor/pear/mail/Mail/mail.php | 10 - .../civicrm/vendor/pear/mail/Mail/smtp.php | 107 +- .../civicrm/vendor/pear/mail/Mail/smtpmx.php | 45 +- .../pear/net_smtp/.github/workflows/ci.yml | 31 - .../civicrm/vendor/pear/net_smtp/Net/SMTP.php | 414 +- .../vendor/pear/net_smtp/composer.json | 5 + .../vendor/pear/net_socket/.travis.yml | 21 + .../civicrm/vendor/pear/net_socket/LICENSE | 9 + .../vendor/pear/net_socket/Net/Socket.php | 223 +- .../civicrm/vendor/pear/net_socket/README.md | 43 + .../vendor/pear/net_socket/composer.json | 9 +- .../vendor/pear/net_socket/package.xml | 24 +- .../vendor/pear/net_socket/phpunit.xml.dist | 29 + .../phenx/php-svg-lib/src/Svg/Document.php | 2 +- .../phenx/php-svg-lib/src/Svg/Style.php | 27 +- .../php-svg-lib/src/Svg/Tag/AbstractTag.php | 2 +- .../phenx/php-svg-lib/src/Svg/Tag/Image.php | 6 +- .../vendor/phpseclib/phpseclib/AUTHORS | 1 + .../vendor/phpseclib/phpseclib/BACKERS.md | 11 +- .../vendor/phpseclib/phpseclib/README.md | 12 +- .../vendor/phpseclib/phpseclib/composer.json | 3 +- .../phpseclib/phpseclib/Crypt/AES.php | 42 +- .../phpseclib/phpseclib/Crypt/Base.php | 227 +- .../phpseclib/phpseclib/Crypt/Blowfish.php | 446 +- .../phpseclib/phpseclib/Crypt/DES.php | 10 +- .../phpseclib/phpseclib/Crypt/Hash.php | 2 +- .../phpseclib/phpseclib/Crypt/RC2.php | 8 +- .../phpseclib/phpseclib/Crypt/RC4.php | 12 +- .../phpseclib/phpseclib/Crypt/RSA.php | 128 +- .../phpseclib/phpseclib/Crypt/Random.php | 5 +- .../phpseclib/phpseclib/Crypt/Rijndael.php | 43 +- .../phpseclib/phpseclib/Crypt/TripleDES.php | 12 +- .../phpseclib/phpseclib/Crypt/Twofish.php | 36 + .../phpseclib/phpseclib/File/ASN1.php | 29 +- .../phpseclib/phpseclib/File/X509.php | 32 +- .../phpseclib/phpseclib/Math/BigInteger.php | 65 +- .../phpseclib/phpseclib/phpseclib/Net/SCP.php | 7 + .../phpseclib/phpseclib/Net/SFTP.php | 921 +- .../phpseclib/phpseclib/Net/SSH1.php | 20 +- .../phpseclib/phpseclib/Net/SSH2.php | 573 +- .../phpseclib/phpseclib/System/SSH/Agent.php | 20 +- .../phpseclib/phpseclib/bootstrap.php | 3 +- .../php-weasyprint/.php-cs-fixer.dist.php | 12 +- .../pontedilana/php-weasyprint/CHANGELOG.md | 38 + .../pontedilana/php-weasyprint/README.md | 16 +- .../pontedilana/php-weasyprint/composer.json | 4 +- .../php-weasyprint/src/AbstractGenerator.php | 103 +- .../php-weasyprint/src/GeneratorInterface.php | 24 +- .../pontedilana/php-weasyprint/src/Image.php | 78 - .../pontedilana/php-weasyprint/src/Pdf.php | 59 +- .../php-weasyprint/src/Version.php | 56 - .../vendor/psr/log/Psr/Log/AbstractLogger.php | 32 +- .../psr/log/Psr/Log/LoggerAwareTrait.php | 2 +- .../civicrm/vendor/psr/log/composer.json | 2 +- .../sabberworm/php-css-parser/CHANGELOG.md | 39 +- .../sabberworm/php-css-parser/README.md | 43 +- .../sabberworm/php-css-parser/composer.json | 16 +- .../src/CSSList/AtRuleBlockList.php | 5 +- .../php-css-parser/src/CSSList/CSSList.php | 43 +- .../php-css-parser/src/CSSList/Document.php | 15 +- .../php-css-parser/src/CSSList/KeyFrame.php | 5 +- .../php-css-parser/src/OutputFormat.php | 22 +- .../php-css-parser/src/OutputFormatter.php | 24 + .../sabberworm/php-css-parser/src/Parser.php | 8 +- .../php-css-parser/src/Parsing/Anchor.php | 34 + .../src/Parsing/ParserState.php | 39 +- .../php-css-parser/src/Property/Charset.php | 22 +- .../php-css-parser/src/Property/Import.php | 10 +- .../php-css-parser/src/Rule/Rule.php | 15 +- .../php-css-parser/src/RuleSet/AtRuleSet.php | 10 +- .../src/RuleSet/DeclarationBlock.php | 13 +- .../php-css-parser/src/RuleSet/RuleSet.php | 20 +- .../php-css-parser/src/Settings.php | 19 +- .../php-css-parser/src/Value/CSSFunction.php | 25 + .../php-css-parser/src/Value/CSSString.php | 5 + .../php-css-parser/src/Value/CalcFunction.php | 23 +- .../php-css-parser/src/Value/Color.php | 9 +- .../src/Value/RuleValueList.php | 5 + .../php-css-parser/src/Value/Size.php | 14 +- .../php-css-parser/src/Value/URL.php | 17 +- .../php-css-parser/src/Value/Value.php | 37 +- .../php-css-parser/src/Value/ValueList.php | 6 + .../vendor/symfony/polyfill-php80/LICENSE | 2 +- .../symfony/polyfill-php80/composer.json | 3 - .../vendor/symfony/process/CHANGELOG.md | 20 + .../symfony/process/ExecutableFinder.php | 12 +- .../vendor/symfony/process/InputStream.php | 6 +- .../civicrm/vendor/symfony/process/LICENSE | 2 +- .../symfony/process/PhpExecutableFinder.php | 12 +- .../vendor/symfony/process/PhpProcess.php | 18 +- .../symfony/process/Pipes/AbstractPipes.php | 4 +- .../symfony/process/Pipes/UnixPipes.php | 5 +- .../symfony/process/Pipes/WindowsPipes.php | 7 +- .../vendor/symfony/process/Process.php | 216 +- .../vendor/symfony/process/ProcessUtils.php | 6 +- .../civicrm/vendor/symfony/process/README.md | 15 + .../vendor/symfony/process/composer.json | 2 +- .../vendor/tecnickcom/tcpdf/CHANGELOG.TXT | 60 + .../vendor/tecnickcom/tcpdf/LICENSE.TXT | 2 +- .../civicrm/vendor/tecnickcom/tcpdf/README.md | 7 +- .../civicrm/vendor/tecnickcom/tcpdf/VERSION | 2 +- .../vendor/tecnickcom/tcpdf/composer.json | 11 +- .../tecnickcom/tcpdf/config/tcpdf_config.php | 8 + .../tcpdf/include/barcodes/datamatrix.php | 2 +- .../tcpdf/include/barcodes/pdf417.php | 8 +- .../tcpdf/include/barcodes/qrcode.php | 11 +- .../tecnickcom/tcpdf/include/tcpdf_colors.php | 2 +- .../tecnickcom/tcpdf/include/tcpdf_fonts.php | 53 +- .../tecnickcom/tcpdf/include/tcpdf_static.php | 56 +- .../civicrm/vendor/tecnickcom/tcpdf/tcpdf.php | 398 +- .../tecnickcom/tcpdf/tcpdf_autoconfig.php | 4 + .../tecnickcom/tcpdf/tcpdf_barcodes_1d.php | 6 +- .../vendor/tecnickcom/tcpdf/tcpdf_import.php | 2 +- .../xml/schema/Contact/SavedSearch.xml | 2 +- .../civicrm/xml/schema/Contribute/Product.xml | 4 +- .../civicrm/xml/schema/Core/County.xml | 2 +- .../civicrm/xml/schema/Core/CustomField.xml | 16 + www/modules/civicrm/xml/schema/Core/Job.xml | 11 + .../civicrm/xml/schema/Core/MailSettings.xml | 4 +- .../civicrm/xml/schema/Core/OpenID.xml | 3 + .../civicrm/xml/schema/Core/Persistent.xml | 2 +- .../civicrm/xml/schema/Core/StateProvince.xml | 2 +- .../civicrm/xml/schema/Core/UFGroup.xml | 35 +- www/modules/civicrm/xml/schema/Cxn/Cxn.xml | 2 +- .../civicrm/xml/schema/Event/Event.xml | 3 +- .../civicrm/xml/schema/Event/Participant.xml | 10 + .../Mailing/Event/MailingEventQueue.xml | 1 + .../xml/schema/Member/MembershipStatus.xml | 3 + .../xml/schema/Member/MembershipType.xml | 5 + www/modules/civicrm/xml/schema/PCP/PCP.xml | 8 + .../xml/schema/Report/ReportInstance.xml | 2 +- .../civicrm/xml/schema/SMS/Provider.xml | 2 +- .../xml/templates/civicrm_navigation.tpl | 6 +- www/modules/civicrm/xml/templates/dao.tpl | 21 +- .../message_templates/case_activity_html.tpl | 2 +- .../message_templates/case_activity_text.tpl | 40 - .../contribution_dupalert_text.tpl | 17 - .../contribution_invoice_receipt_html.tpl | 54 +- .../contribution_invoice_receipt_text.tpl | 1 - .../contribution_offline_receipt_text.tpl | 115 - .../contribution_recurring_edit_text.tpl | 8 - .../event_online_receipt_html.tpl | 6 +- .../message_templates/friend_text.tpl | 15 - .../membership_autorenew_billing_text.tpl | 23 - .../membership_autorenew_cancelled_text.tpl | 14 - .../membership_offline_receipt_text.tpl | 103 - .../membership_online_receipt_html.tpl | 8 +- .../participant_cancelled_html.tpl | 109 +- .../participant_confirm_html.tpl | 128 +- .../participant_expired_html.tpl | 120 +- .../participant_transferred_html.tpl | 107 +- .../pcp_owner_notify_text.tpl | 22 - .../message_templates/uf_notify_html.tpl | 4 +- .../message_templates/uf_notify_subject.tpl | 2 +- www/modules/civicrm/xml/version.xml | 2 +- 2521 files changed, 177758 insertions(+), 31009 deletions(-) delete mode 100644 www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Upgrader/Base.php create mode 100644 www/civi-extensions/uk.co.vedaconsulting.gdpr/composer.json create mode 100644 www/civi-extensions/uk.co.vedaconsulting.gdpr/docs/hooks.md create mode 100644 www/civi-extensions/uk.co.vedaconsulting.gdpr/mixin/menu-xml@1.0.0.mixin.php create mode 100644 www/civi-extensions/uk.co.vedaconsulting.gdpr/mixin/mgd-php@1.0.0.mixin.php create mode 100644 www/civi-extensions/uk.co.vedaconsulting.gdpr/mixin/polyfill.php create mode 100644 www/civi-extensions/uk.co.vedaconsulting.gdpr/mixin/smarty-v2@1.0.1.mixin.php delete mode 100644 www/modules/civicrm/CRM/Activity/Import/Controller.php create mode 100644 www/modules/civicrm/CRM/Campaign/Form/CampaignFormTrait.php create mode 100644 www/modules/civicrm/CRM/Case/Form/CaseFormTrait.php create mode 100644 www/modules/civicrm/CRM/Case/WorkflowMessage/ActivityExamples.php create mode 100644 www/modules/civicrm/CRM/Case/WorkflowMessage/CaseActivity.php create mode 100644 www/modules/civicrm/CRM/Contact/Form/Task/SMSTrait.php delete mode 100644 www/modules/civicrm/CRM/Contact/Import/Controller.php delete mode 100644 www/modules/civicrm/CRM/Contribute/Import/Controller.php create mode 100644 www/modules/civicrm/CRM/Core/CodeGen/PhpSchema.php create mode 100644 www/modules/civicrm/CRM/Core/DAO/Base.php delete mode 100644 www/modules/civicrm/CRM/Core/I18n/SchemaStructure_4_2_alpha1.php delete mode 100644 www/modules/civicrm/CRM/Core/I18n/SchemaStructure_4_3_1.php delete mode 100644 www/modules/civicrm/CRM/Core/I18n/SchemaStructure_4_5_alpha1.php delete mode 100644 www/modules/civicrm/CRM/Core/I18n/SchemaStructure_4_5_beta2.php delete mode 100644 www/modules/civicrm/CRM/Core/I18n/SchemaStructure_4_7_alpha1.php create mode 100644 www/modules/civicrm/CRM/Core/Smarty/UserContentPolicy.php create mode 100644 www/modules/civicrm/CRM/Core/Smarty/plugins/modifier.crmEscapeSingleQuotes.php create mode 100644 www/modules/civicrm/CRM/Core/WorkflowMessage/Profile/Profile.php create mode 100644 www/modules/civicrm/CRM/Core/WorkflowMessage/UFNotify.php delete mode 100644 www/modules/civicrm/CRM/Custom/Import/Controller.php delete mode 100644 www/modules/civicrm/CRM/Event/Import/Controller.php create mode 100644 www/modules/civicrm/CRM/Event/WorkflowMessage/ParticipantConfirm.php create mode 100644 www/modules/civicrm/CRM/Event/WorkflowMessage/ParticipantExpired.php create mode 100644 www/modules/civicrm/CRM/Import/Controller.php delete mode 100644 www/modules/civicrm/CRM/Member/Import/Controller.php create mode 100644 www/modules/civicrm/CRM/Pledge/Form/PledgeFormTrait.php create mode 100644 www/modules/civicrm/CRM/SMS/BAO/SmsProvider.php create mode 100644 www/modules/civicrm/CRM/SMS/DAO/SmsProvider.php create mode 100644 www/modules/civicrm/CRM/Upgrade/Incremental/php/FiveSeventyFour.php create mode 100644 www/modules/civicrm/CRM/Upgrade/Incremental/php/FiveSeventyOne.php create mode 100644 www/modules/civicrm/CRM/Upgrade/Incremental/php/FiveSeventyThree.php create mode 100644 www/modules/civicrm/CRM/Upgrade/Incremental/php/FiveSeventyTwo.php create mode 100644 www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.71.alpha1.mysql.tpl create mode 100644 www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.71.beta1.mysql.tpl create mode 100644 www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.72.alpha1.mysql.tpl create mode 100644 www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.73.alpha1.mysql.tpl create mode 100644 www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.74.alpha1.mysql.tpl create mode 100644 www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.74.beta1.mysql.tpl create mode 100644 www/modules/civicrm/CRM/Utils/Schema.php delete mode 100644 www/modules/civicrm/Civi/Api4/Action/CustomValue/GetFields.php create mode 100644 www/modules/civicrm/Civi/Api4/Service/Autocomplete/PhoneAutocompleteProvider.php rename www/modules/civicrm/Civi/Api4/{Provider.php => SmsProvider.php} (93%) create mode 100644 www/modules/civicrm/Civi/Custom/EventSubscriber/FkEntityDeleteSubscriber.php create mode 100644 www/modules/civicrm/Civi/Schema/EntityMetadataBase.php create mode 100644 www/modules/civicrm/Civi/Schema/EntityMetadataInterface.php create mode 100644 www/modules/civicrm/Civi/Schema/EntityProvider.php create mode 100644 www/modules/civicrm/Civi/Schema/EntityRepository.php create mode 100644 www/modules/civicrm/Civi/Schema/EntityStorageInterface.php create mode 100644 www/modules/civicrm/Civi/Schema/LegacySqlEntityMetadata.php create mode 100644 www/modules/civicrm/Civi/Schema/SqlEntityMetadata.php rename www/modules/civicrm/{CRM/UF/Page/AJAX.php => Civi/Schema/SqlEntityStorage.php} (51%) create mode 100644 www/modules/civicrm/backdrop/icons/civicrm.svg create mode 100644 www/modules/civicrm/ext/afform/core/ang/afblockParticipantNote.aff.html create mode 100644 www/modules/civicrm/ext/afform/core/ang/afblockParticipantNote.aff.php delete mode 100644 www/modules/civicrm/ext/afform/html/images/screenshot.png delete mode 100644 www/modules/civicrm/ext/authx/images/screenshot.png create mode 100644 www/modules/civicrm/ext/civi_contribute/Civi/Api4/Service/ContributionTasksProvider.php create mode 100644 www/modules/civicrm/ext/civi_event/Civi/Api4/Service/Links/ParticipantLinksProvider.php create mode 100644 www/modules/civicrm/ext/civi_mail/Civi/Api4/Action/Mailing/CreateAction.php rename www/modules/civicrm/{CRM/Mailing/Page/Optout.php => ext/civi_mail/Civi/Api4/Action/Mailing/MailingSaveTrait.php} (59%) create mode 100644 www/modules/civicrm/ext/civi_mail/Civi/Api4/Action/Mailing/SaveAction.php create mode 100644 www/modules/civicrm/ext/civi_mail/Civi/Api4/Action/Mailing/UpdateAction.php create mode 100644 www/modules/civicrm/ext/civi_mail/Civi/Api4/Service/MailTasksProvider.php create mode 100644 www/modules/civicrm/ext/civi_member/Civi/Api4/Service/Links/MembershipLinksProvider.php create mode 100644 www/modules/civicrm/ext/civi_member/Civi/Api4/Service/Links/MembershipStatusLinksProvider.php create mode 100644 www/modules/civicrm/ext/civi_member/Civi/Api4/Service/Spec/Provider/MembershipGetSpecProvider.php create mode 100644 www/modules/civicrm/ext/civicrm_admin_ui/ang/afformTabMember.aff.html create mode 100644 www/modules/civicrm/ext/civicrm_admin_ui/ang/afformTabMember.aff.php create mode 100644 www/modules/civicrm/ext/civicrm_admin_ui/ang/afsearchPrintPagePDFFormats.aff.html create mode 100644 www/modules/civicrm/ext/civicrm_admin_ui/ang/afsearchPrintPagePDFFormats.aff.php create mode 100644 www/modules/civicrm/ext/civicrm_admin_ui/ang/afsearchSMSProvider.aff.html create mode 100644 www/modules/civicrm/ext/civicrm_admin_ui/ang/afsearchSMSProvider.aff.php create mode 100644 www/modules/civicrm/ext/civicrm_admin_ui/ang/afsearchTabMember.aff.html create mode 100644 www/modules/civicrm/ext/civicrm_admin_ui/ang/afsearchTabMember.aff.php create mode 100644 www/modules/civicrm/ext/civicrm_admin_ui/ang/afsearchTabParticipant.aff.html create mode 100644 www/modules/civicrm/ext/civicrm_admin_ui/ang/afsearchTabParticipant.aff.php create mode 100644 www/modules/civicrm/ext/civicrm_admin_ui/managed/SavedSearch_Contact_Summary_Events.mgd.php create mode 100644 www/modules/civicrm/ext/civicrm_admin_ui/managed/SavedSearch_Contact_Summary_Membership_Type.mgd.php create mode 100644 www/modules/civicrm/ext/civicrm_admin_ui/managed/SavedSearch_Contact_Summary_Memberships.mgd.php create mode 100644 www/modules/civicrm/ext/civicrm_admin_ui/managed/SavedSearch_Print_Page_PDF_Formats.mgd.php create mode 100644 www/modules/civicrm/ext/civicrm_admin_ui/managed/SavedSearch_SMS_Provider.mgd.php create mode 100644 www/modules/civicrm/ext/civicrm_search_ui/ang/afsearchFindMemberships.aff.html create mode 100644 www/modules/civicrm/ext/civicrm_search_ui/ang/afsearchFindMemberships.aff.json create mode 100644 www/modules/civicrm/ext/civicrm_search_ui/managed/SavedSearch_Find_Memberships.mgd.php delete mode 100644 www/modules/civicrm/ext/civiimport/images/screenshot.png delete mode 100644 www/modules/civicrm/ext/ckeditor4/images/screenshot.png delete mode 100644 www/modules/civicrm/ext/eventcart/images/screenshot.png delete mode 100644 www/modules/civicrm/ext/ewaysingle/images/screenshot.png delete mode 100644 www/modules/civicrm/ext/financialacls/images/screenshot.png create mode 100644 www/modules/civicrm/ext/flexmailer/src/Listener/IsActiveTrait.php delete mode 100644 www/modules/civicrm/ext/iatspayments/CRM/Iats/Form/Settings.php create mode 100644 www/modules/civicrm/ext/iatspayments/CRM/Iats/Utils.php create mode 100644 www/modules/civicrm/ext/iatspayments/release-notes/1.9.0.md create mode 100644 www/modules/civicrm/ext/iatspayments/settings/iats.setting.php delete mode 100644 www/modules/civicrm/ext/iatspayments/templates/CRM/Iats/Form/Settings.tpl create mode 100644 www/modules/civicrm/ext/iatspayments/tests/phpunit/CRM/Iats/SettingsTest.php create mode 100644 www/modules/civicrm/ext/iframe/CRM/Iframe/Page/InstallPage.php create mode 100644 www/modules/civicrm/ext/iframe/CRM/Iframe/Utils.php create mode 100644 www/modules/civicrm/ext/iframe/Civi/Api4/Iframe.php create mode 100644 www/modules/civicrm/ext/iframe/Civi/Iframe/Cosession.php create mode 100644 www/modules/civicrm/ext/iframe/Civi/Iframe/EntryPoint/Backdrop.php create mode 100644 www/modules/civicrm/ext/iframe/Civi/Iframe/EntryPoint/Drupal.php create mode 100644 www/modules/civicrm/ext/iframe/Civi/Iframe/EntryPoint/Drupal8.php create mode 100644 www/modules/civicrm/ext/iframe/Civi/Iframe/Iframe.php create mode 100644 www/modules/civicrm/ext/iframe/Civi/Iframe/Router.php create mode 100644 www/modules/civicrm/ext/iframe/Civi/Iframe/ScriptManager.php create mode 100644 www/modules/civicrm/ext/iframe/README.md create mode 100644 www/modules/civicrm/ext/iframe/iframe.civix.php create mode 100644 www/modules/civicrm/ext/iframe/iframe.php create mode 100644 www/modules/civicrm/ext/iframe/info.xml create mode 100644 www/modules/civicrm/ext/iframe/lib/IframeDrupalKernel.php create mode 100644 www/modules/civicrm/ext/iframe/lib/IframeSessionConfiguration.php create mode 100644 www/modules/civicrm/ext/iframe/settings/iframe.setting.php create mode 100644 www/modules/civicrm/ext/iframe/templates/CRM/Iframe/Page/InstallPage.tpl create mode 100644 www/modules/civicrm/ext/iframe/templates/iframe-basic-page.tpl create mode 100644 www/modules/civicrm/ext/iframe/xml/Menu/iframe.xml delete mode 100644 www/modules/civicrm/ext/legacycustomsearches/images/screenshot.png delete mode 100644 www/modules/civicrm/ext/message_admin/images/screenshot.png create mode 100644 www/modules/civicrm/ext/oembed/CRM/Oembed/Page/Oembed.php create mode 100644 www/modules/civicrm/ext/oembed/CRM/Oembed/Page/OembedShare.php create mode 100644 www/modules/civicrm/ext/oembed/Civi/Oembed/Oembed.php create mode 100644 www/modules/civicrm/ext/oembed/Civi/Oembed/OembedDiscovery.php create mode 100644 www/modules/civicrm/ext/oembed/LICENSE.txt create mode 100644 www/modules/civicrm/ext/oembed/README.md create mode 100644 www/modules/civicrm/ext/oembed/ang/crmOembedSharing.ang.php create mode 100644 www/modules/civicrm/ext/oembed/ang/crmOembedSharing.css create mode 100644 www/modules/civicrm/ext/oembed/ang/crmOembedSharing.js create mode 100644 www/modules/civicrm/ext/oembed/ang/crmOembedSharing/main.html create mode 100644 www/modules/civicrm/ext/oembed/ang/crmOembedSharing/main.js create mode 100644 www/modules/civicrm/ext/oembed/info.xml create mode 100644 www/modules/civicrm/ext/oembed/oembed.civix.php create mode 100644 www/modules/civicrm/ext/oembed/oembed.php create mode 100644 www/modules/civicrm/ext/oembed/phpunit.xml.dist create mode 100644 www/modules/civicrm/ext/oembed/settings/oembed.setting.php create mode 100644 www/modules/civicrm/ext/oembed/templates/CRM/Oembed/Page/Oembed.tpl create mode 100644 www/modules/civicrm/ext/oembed/templates/CRM/Oembed/Page/OembedShare.tpl create mode 100644 www/modules/civicrm/ext/oembed/tests/phpunit/CRM/Oembed/Page/OembedDiscoveryTest.php create mode 100644 www/modules/civicrm/ext/oembed/tests/phpunit/CRM/Oembed/Page/OembedTest.php create mode 100644 www/modules/civicrm/ext/oembed/tests/phpunit/bootstrap.php create mode 100644 www/modules/civicrm/ext/oembed/xml/Menu/oembed.xml delete mode 100644 www/modules/civicrm/ext/payflowpro/images/screenshot.png create mode 100644 www/modules/civicrm/ext/standaloneusers/tests/phpunit/Civi/Api4/Action/UserTest.php create mode 100644 www/modules/civicrm/functions.php create mode 100644 www/modules/civicrm/mixin/entity-types-php@2/mixin.php create mode 100644 www/modules/civicrm/mixin/lib/civimix-schema/pathload.main.php create mode 100644 www/modules/civicrm/mixin/lib/civimix-schema/src/AutomaticUpgrader.php create mode 100644 www/modules/civicrm/mixin/lib/civimix-schema/src/CiviMixSchema.php create mode 100644 www/modules/civicrm/mixin/lib/civimix-schema/src/SchemaHelper.php create mode 100644 www/modules/civicrm/mixin/lib/civimix-schema/src/SchemaHelperInterface.php create mode 100644 www/modules/civicrm/mixin/lib/civimix-schema/src/SqlGenerator.php create mode 100644 www/modules/civicrm/mixin/lib/pathload-0.php create mode 100644 www/modules/civicrm/mixin/lib/pathload.index.php create mode 100644 www/modules/civicrm/mixin/smarty@1/mixin.php create mode 100644 www/modules/civicrm/packages/smarty4/composer.json create mode 100644 www/modules/civicrm/packages/smarty4/composer.lock create mode 100644 www/modules/civicrm/packages/smarty4/vendor/autoload.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/composer/ClassLoader.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/composer/InstalledVersions.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/composer/LICENSE create mode 100644 www/modules/civicrm/packages/smarty4/vendor/composer/autoload_classmap.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/composer/autoload_namespaces.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/composer/autoload_psr4.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/composer/autoload_real.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/composer/autoload_static.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/composer/installed.json create mode 100644 www/modules/civicrm/packages/smarty4/vendor/composer/installed.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/composer/platform_check.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/CHANGELOG.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/LICENSE create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/README.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/SECURITY.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/changelog/.gitkeep create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/composer.json create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/demo/configs/test.conf create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/demo/index.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/demo/plugins/cacheresource.apc.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/demo/plugins/cacheresource.memcache.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/demo/plugins/cacheresource.mysql.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/demo/plugins/cacheresource.pdo.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/demo/plugins/cacheresource.pdo_gzip.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/demo/plugins/resource.extendsall.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/demo/plugins/resource.mysql.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/demo/plugins/resource.mysqls.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/demo/templates/footer.tpl create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/demo/templates/header.tpl create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/demo/templates/index.tpl create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/_config.yml create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/appendixes/tips.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/appendixes/troubleshooting.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/chapter-debugging-console.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/config-files.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-basic-syntax/index.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-basic-syntax/language-escaping.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-basic-syntax/language-math.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-basic-syntax/language-syntax-attributes.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-basic-syntax/language-syntax-comments.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-basic-syntax/language-syntax-functions.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-basic-syntax/language-syntax-quotes.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-basic-syntax/language-syntax-variables.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/index.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-append.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-assign.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-block.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-call.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-capture.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-config-load.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-debug.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-extends.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-for.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-foreach.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-function.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-if.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-include.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-insert.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-ldelim.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-literal.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-nocache.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-section.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-setfilter.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-strip.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-while.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-combining-modifiers.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-custom-functions/index.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-counter.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-cycle.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-debug.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-eval.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-fetch.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-html-checkboxes.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-html-image.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-html-options.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-html-radios.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-html-select-date.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-html-select-time.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-html-table.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-mailto.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-math.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-textformat.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/index.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-capitalize.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-cat.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-count-characters.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-count-paragraphs.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-count-sentences.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-count-words.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-date-format.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-default.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-escape.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-from-charset.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-indent.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-lower.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-nl2br.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-regex-replace.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-replace.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-spacify.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-string-format.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-strip-tags.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-strip.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-to-charset.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-truncate.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-unescape.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-upper.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-wordwrap.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-variables/index.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-variables/language-assigned-variables.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-variables/language-config-variables.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-variables/language-variable-scopes.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/designers/language-variables/language-variables-smarty.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/features.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/getting-started.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/index.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/philosophy.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/advanced-features.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/advanced-features/advanced-features-objects.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/advanced-features/advanced-features-outputfilters.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/advanced-features/advanced-features-postfilters.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/advanced-features/advanced-features-prefilters.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/advanced-features/advanced-features-security.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/advanced-features/advanced-features-static-classes.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/advanced-features/advanced-features-streams.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/advanced-features/advanced-features-template-inheritance.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/advanced-features/advanced-features-template-settings.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-add-config-dir.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-add-plugins-dir.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-add-template-dir.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-append-by-ref.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-append.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-assign-by-ref.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-assign.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-clear-all-assign.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-clear-all-cache.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-clear-assign.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-clear-cache.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-clear-compiled-tpl.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-clear-config.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-compile-all-config.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-compile-all-templates.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-config-load.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-create-data.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-create-template.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-disable-security.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-display.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-enable-security.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-fetch.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-get-cache-dir.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-get-compile-dir.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-get-config-dir.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-get-config-vars.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-get-plugins-dir.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-get-registered-object.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-get-tags.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-get-template-dir.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-get-template-vars.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-is-cached.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-load-filter.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-mute-expected-errors.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-register-cacheresource.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-register-class.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-register-default-plugin-handler.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-register-filter.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-register-object.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-register-plugin.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-register-resource.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-set-cache-dir.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-set-compile-dir.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-set-config-dir.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-set-plugins-dir.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-set-template-dir.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-template-exists.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-test-install.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-unregister-cacheresource.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-unregister-filter.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-unregister-object.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-unregister-plugin.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-functions/api-unregister-resource.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-allow-php-templates.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-auto-literal.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-autoload-filters.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-cache-dir.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-cache-id.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-cache-lifetime.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-cache-locking.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-cache-modified-check.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-caching-type.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-caching.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-compile-check.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-compile-dir.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-compile-id.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-compile-locking.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-compiler-class.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-config-booleanize.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-config-dir.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-config-overwrite.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-config-read-hidden.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-debug-template.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-debugging-ctrl.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-debugging.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-default-config-handler-func.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-default-config-type.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-default-modifiers.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-default-resource-type.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-default-template-handler-func.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-direct-access-security.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-error-reporting.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-escape-html.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-force-cache.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-force-compile.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-left-delimiter.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-locking-timeout.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-merge-compiled-includes.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-plugins-dir.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-right-delimiter.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-smarty-debug-id.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-template-dir.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-trusted-dir.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-use-include-path.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/api-variables/variable-use-sub-dirs.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/caching.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/caching/caching-cacheable.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/caching/caching-custom.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/caching/caching-groups.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/caching/caching-multiple-caches.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/caching/caching-setting-up.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/charset.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/plugins.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/plugins/plugins-block-functions.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/plugins/plugins-compiler-functions.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/plugins/plugins-functions.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/plugins/plugins-howto.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/plugins/plugins-inserts.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/plugins/plugins-modifiers.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/plugins/plugins-naming-conventions.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/plugins/plugins-outputfilters.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/plugins/plugins-prefilters-postfilters.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/plugins/plugins-resources.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/plugins/plugins-writing.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/resources.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/resources/resources-custom.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/resources/resources-extends.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/resources/resources-file.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/resources/resources-streams.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/resources/resources-string.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/resources/template-resources.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/programmers/smarty-constants.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/docs/upgrading.md create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/lexer/smarty_internal_configfilelexer.plex create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/lexer/smarty_internal_configfileparser.y create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/lexer/smarty_internal_templatelexer.plex create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/lexer/smarty_internal_templateparser.y create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/Autoloader.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/Smarty.class.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/bootstrap.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/debug.tpl create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/functions.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/block.textformat.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/function.counter.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/function.cycle.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/function.fetch.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/function.html_checkboxes.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/function.html_image.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/function.html_options.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/function.html_radios.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/function.html_select_date.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/function.html_select_time.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/function.html_table.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/function.mailto.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/function.math.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifier.capitalize.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifier.count.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifier.date_format.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifier.debug_print_var.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifier.escape.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifier.explode.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifier.implode.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifier.mb_wordwrap.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifier.number_format.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifier.regex_replace.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifier.replace.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifier.spacify.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifier.truncate.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.cat.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.count_characters.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.count_paragraphs.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.count_sentences.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.count_words.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.default.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.escape.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.from_charset.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.indent.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.json_encode.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.lower.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.nl2br.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.noprint.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.round.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.str_repeat.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.string_format.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.strip.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.strip_tags.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.strlen.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.substr.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.to_charset.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.unescape.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.upper.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/modifiercompiler.wordwrap.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/outputfilter.trimwhitespace.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/shared.escape_special_chars.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/shared.literal_compiler_param.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/shared.make_timestamp.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/shared.mb_str_replace.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/shared.mb_unicode.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/plugins/variablefilter.htmlspecialchars.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_cacheresource.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_cacheresource_custom.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_cacheresource_keyvaluestore.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_data.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_block.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_cacheresource_file.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_append.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_assign.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_block.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_block_child.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_block_parent.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_break.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_call.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_capture.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_child.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_config_load.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_continue.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_debug.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_eval.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_extends.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_for.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_foreach.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_function.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_if.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_include.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_insert.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_ldelim.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_make_nocache.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_nocache.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_parent.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_private_block_plugin.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_private_foreachsection.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_private_function_plugin.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_private_modifier.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_private_object_block_function.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_private_object_function.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_private_print_expression.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_private_registered_block.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_private_registered_function.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_private_special_variable.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_rdelim.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_section.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_setfilter.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_shared_inheritance.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compile_while.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_compilebase.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_config_file_compiler.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_configfilelexer.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_configfileparser.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_data.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_debug.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_errorhandler.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_extension_handler.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_addautoloadfilters.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_adddefaultmodifiers.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_append.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_appendbyref.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_assignbyref.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_assignglobal.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_clearallassign.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_clearallcache.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_clearassign.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_clearcache.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_clearcompiledtemplate.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_clearconfig.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_compileallconfig.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_compilealltemplates.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_configload.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_createdata.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_getautoloadfilters.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_getconfigvariable.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_getconfigvars.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_getdebugtemplate.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_getdefaultmodifiers.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_getglobal.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_getregisteredobject.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_getstreamvariable.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_gettags.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_gettemplatevars.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_literals.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_loadfilter.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_loadplugin.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_mustcompile.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_registercacheresource.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_registerclass.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_registerdefaultconfighandler.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_registerdefaultpluginhandler.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_registerdefaulttemplatehandler.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_registerfilter.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_registerobject.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_registerplugin.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_registerresource.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_setautoloadfilters.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_setdebugtemplate.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_setdefaultmodifiers.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_unloadfilter.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_unregistercacheresource.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_unregisterfilter.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_unregisterobject.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_unregisterplugin.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_method_unregisterresource.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_nocache_insert.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_parsetree.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_parsetree_code.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_parsetree_dq.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_parsetree_dqcontent.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_parsetree_tag.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_parsetree_template.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_parsetree_text.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_resource_eval.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_resource_extends.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_resource_file.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_resource_php.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_resource_stream.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_resource_string.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_runtime_cachemodify.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_runtime_cacheresourcefile.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_runtime_capture.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_runtime_codeframe.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_runtime_filterhandler.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_runtime_foreach.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_runtime_getincludepath.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_runtime_inheritance.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_runtime_make_nocache.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_runtime_tplfunction.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_runtime_updatecache.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_runtime_updatescope.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_runtime_writefile.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_smartytemplatecompiler.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_template.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_templatebase.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_templatecompilerbase.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_templatelexer.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_templateparser.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_testinstall.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_internal_undefined.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_resource.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_resource_custom.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_resource_recompiled.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_resource_uncompiled.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_security.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_template_cached.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_template_compiled.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_template_config.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_template_resource_base.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_template_source.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_undefined_variable.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smarty_variable.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smartycompilerexception.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/libs/sysplugins/smartyexception.php create mode 100644 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/mkdocs.yml create mode 100755 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/run-tests-for-all-php-versions.sh create mode 100755 www/modules/civicrm/packages/smarty4/vendor/smarty/smarty/run-tests.sh create mode 100644 www/modules/civicrm/packages/smarty5/Smarty.php create mode 100644 www/modules/civicrm/packages/smarty5/composer.json create mode 100644 www/modules/civicrm/packages/smarty5/composer.lock create mode 100644 www/modules/civicrm/packages/smarty5/vendor/autoload.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/composer/ClassLoader.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/composer/InstalledVersions.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/composer/LICENSE create mode 100644 www/modules/civicrm/packages/smarty5/vendor/composer/autoload_classmap.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/composer/autoload_files.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/composer/autoload_namespaces.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/composer/autoload_psr4.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/composer/autoload_real.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/composer/autoload_static.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/composer/installed.json create mode 100644 www/modules/civicrm/packages/smarty5/vendor/composer/installed.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/composer/platform_check.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/CHANGELOG.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/CONTRIBUTING.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/LICENSE create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/Makefile create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/README.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/SECURITY.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/TODO.txt create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/changelog/.gitkeep create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/composer.json create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/demo/configs/test.conf create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/demo/index.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/demo/templates/footer.tpl create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/demo/templates/header.tpl create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/demo/templates/index.tpl create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/_config.yml create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/basics.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/caching/basics.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/caching/custom-storage-layers.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/caching/multiple-caches-per-template.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/configuring.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/extending/block-tags.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/extending/extensions.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/extending/introduction.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/extending/modifiers.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/extending/tags.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/filters/output-filters.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/filters/postfilters.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/filters/prefilters.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/inheritance.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/rendering.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/resources.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/security.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/variables/assigning.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/variables/config-files.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/variables/objects.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/variables/static-class-methods.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/api/variables/streams.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/appendixes/tips.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/appendixes/troubleshooting.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/chapter-debugging-console.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/config-files.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-basic-syntax/index.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-basic-syntax/language-escaping.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-basic-syntax/language-syntax-attributes.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-basic-syntax/language-syntax-comments.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-basic-syntax/language-syntax-operators.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-basic-syntax/language-syntax-quotes.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-basic-syntax/language-syntax-tags.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-basic-syntax/language-syntax-variables.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/index.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-append.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-assign.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-block.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-call.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-capture.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-config-load.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-debug.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-extends.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-for.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-foreach.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-function.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-if.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-include.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-insert.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-ldelim.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-literal.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-nocache.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-section.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-setfilter.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-strip.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-builtin-functions/language-function-while.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-combining-modifiers.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-custom-functions/index.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-counter.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-cycle.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-debug.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-eval.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-fetch.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-html-checkboxes.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-html-image.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-html-options.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-html-radios.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-html-select-date.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-html-select-time.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-html-table.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-mailto.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-math.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-custom-functions/language-function-textformat.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/index.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-capitalize.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-cat.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-count-characters.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-count-paragraphs.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-count-sentences.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-count-words.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-count.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-date-format.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-debug-print-var.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-default.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-escape.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-from-charset.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-indent.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-is_array.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-isset.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-join.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-json-encode.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-lower.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-nl2br.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-noprint.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-number-format.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-regex-replace.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-replace.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-round.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-spacify.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-split.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-str-repeat.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-string-format.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-strip-tags.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-strip.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-strlen.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-substr.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-to-charset.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-truncate.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-unescape.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-upper.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-modifiers/language-modifier-wordwrap.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-variables/index.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-variables/language-assigned-variables.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-variables/language-config-variables.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-variables/language-variable-scopes.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/designers/language-variables/language-variables-smarty.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/features.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/getting-started.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/img/favicon.ico create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/index.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/philosophy.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/add-extension.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-add-plugins-dir.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-append.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-assign.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-clear-all-assign.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-clear-all-cache.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-clear-assign.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-clear-cache.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-clear-compiled-tpl.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-clear-config.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-compile-all-config.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-compile-all-templates.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-config-load.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-create-data.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-create-template.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-disable-security.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-display.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-enable-security.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-fetch.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-get-config-dir.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-get-config-vars.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-get-plugins-dir.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-get-registered-object.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-get-template-vars.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-is-cached.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-load-filter.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-mute-expected-errors.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-register-cacheresource.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-register-class.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-register-default-plugin-handler.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-register-filter.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-register-object.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-register-plugin.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-register-resource.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-set-plugins-dir.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-test-install.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-unregister-cacheresource.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-unregister-filter.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-unregister-object.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-unregister-plugin.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-functions/api-unregister-resource.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-auto-literal.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-cache-dir.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-cache-id.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-cache-lifetime.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-cache-locking.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-cache-modified-check.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-caching-type.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-caching.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-compile-dir.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-compile-id.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-compile-locking.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-compiler-class.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-config-booleanize.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-config-dir.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-config-overwrite.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-config-read-hidden.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-debug-template.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-debugging-ctrl.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-debugging.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-default-config-handler-func.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-default-config-type.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-default-modifiers.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-default-resource-type.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-default-template-handler-func.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-error-reporting.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-escape-html.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-force-cache.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-force-compile.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-locking-timeout.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-merge-compiled-includes.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-smarty-debug-id.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-template-dir.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-use-include-path.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/programmers/api-variables/variable-use-sub-dirs.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/docs/upgrading.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/libs/Smarty.class.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/mkdocs.yml create mode 100755 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/run-tests-for-all-php-versions.sh create mode 100755 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/run-tests.sh create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/BlockHandler/Base.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/BlockHandler/BlockHandlerInterface.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/BlockHandler/BlockPluginWrapper.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/BlockHandler/TextFormat.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Cacheresource/Base.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Cacheresource/Custom.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Cacheresource/File.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Cacheresource/KeyValueStore.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Base.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/BlockCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/CompilerInterface.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/DefaultHandlerBlockCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/DefaultHandlerFunctionCallCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/FunctionCallCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/BCPluginWrapper.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/Base.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/CatModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/CountCharactersModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/CountParagraphsModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/CountSentencesModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/CountWordsModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/DefaultModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/EmptyModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/EscapeModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/FromCharsetModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/IndentModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/IsArrayModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/IssetModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/JsonEncodeModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/LowerModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/ModifierCompilerInterface.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/Nl2brModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/NoPrintModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/RoundModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/StrRepeatModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/StringFormatModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/StripModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/StripTagsModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/StrlenModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/SubstrModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/ToCharsetModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/UnescapeModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/UpperModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Modifier/WordWrapModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/ModifierCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/ObjectMethodBlockCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/ObjectMethodCallCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/PrintExpressionCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/SpecialVariableCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/Append.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/Assign.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/BCPluginWrapper.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/Block.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/BlockClose.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/BreakTag.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/Call.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/Capture.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/CaptureClose.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/ConfigLoad.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/ContinueTag.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/Debug.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/ElseIfTag.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/ElseTag.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/EvalTag.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/ExtendsTag.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/ForClose.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/ForElse.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/ForTag.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/ForeachClose.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/ForeachElse.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/ForeachSection.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/ForeachTag.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/FunctionClose.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/FunctionTag.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/IfClose.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/IfTag.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/IncludeTag.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/Inheritance.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/Ldelim.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/Nocache.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/NocacheClose.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/Rdelim.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/Section.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/SectionClose.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/SectionElse.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/Setfilter.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/SetfilterClose.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/WhileClose.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compile/Tag/WhileTag.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compiler/BaseCompiler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compiler/CodeFrame.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compiler/Configfile.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Compiler/Template.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/CompilerException.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Data.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Debug.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/ErrorHandler.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Exception.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Extension/BCPluginsAdapter.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Extension/Base.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Extension/CallbackWrapper.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Extension/CoreExtension.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Extension/DefaultExtension.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Extension/ExtensionInterface.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Filter/FilterInterface.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Filter/FilterPluginWrapper.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Filter/Output/TrimWhitespace.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/FunctionHandler/BCPluginWrapper.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/FunctionHandler/Base.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/FunctionHandler/Count.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/FunctionHandler/Counter.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/FunctionHandler/Cycle.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/FunctionHandler/Fetch.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/FunctionHandler/FunctionHandlerInterface.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/FunctionHandler/HtmlBase.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/FunctionHandler/HtmlCheckboxes.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/FunctionHandler/HtmlImage.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/FunctionHandler/HtmlOptions.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/FunctionHandler/HtmlRadios.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/FunctionHandler/HtmlSelectDate.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/FunctionHandler/HtmlSelectTime.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/FunctionHandler/HtmlTable.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/FunctionHandler/Mailto.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/FunctionHandler/Math.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Lexer/ConfigfileLexer.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Lexer/ConfigfileLexer.plex create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Lexer/TemplateLexer.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Lexer/TemplateLexer.plex create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/ParseTree/Base.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/ParseTree/Code.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/ParseTree/Dq.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/ParseTree/DqContent.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/ParseTree/Tag.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/ParseTree/Template.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/ParseTree/Text.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Parser/ConfigfileParser.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Parser/ConfigfileParser.y create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Parser/TemplateParser.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Parser/TemplateParser.y create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Resource/BasePlugin.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Resource/CustomPlugin.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Resource/ExtendsPlugin.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Resource/FilePlugin.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Resource/RecompiledPlugin.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Resource/StreamPlugin.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Resource/StringEval.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Resource/StringPlugin.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Runtime/Block.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Runtime/CaptureRuntime.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Runtime/DefaultPluginHandlerRuntime.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Runtime/ForeachRuntime.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Runtime/InheritanceRuntime.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Runtime/TplFunctionRuntime.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Security.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Smarty.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Template.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Template/Cached.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Template/Compiled.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Template/Config.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Template/GeneratedPhpFile.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Template/Source.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/TemplateBase.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/TestInstall.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/UndefinedVariable.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/Variable.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/debug.tpl create mode 100644 www/modules/civicrm/packages/smarty5/vendor/smarty/smarty/src/functions.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/symfony/polyfill-mbstring/LICENSE create mode 100644 www/modules/civicrm/packages/smarty5/vendor/symfony/polyfill-mbstring/Mbstring.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/symfony/polyfill-mbstring/README.md create mode 100644 www/modules/civicrm/packages/smarty5/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/symfony/polyfill-mbstring/bootstrap.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/symfony/polyfill-mbstring/bootstrap80.php create mode 100644 www/modules/civicrm/packages/smarty5/vendor/symfony/polyfill-mbstring/composer.json delete mode 100644 www/modules/civicrm/release-notes/5.70.1.md create mode 100644 www/modules/civicrm/release-notes/5.71.0.md create mode 100644 www/modules/civicrm/release-notes/5.71.1.md create mode 100644 www/modules/civicrm/release-notes/5.71.2.md create mode 100644 www/modules/civicrm/release-notes/5.72.0.md create mode 100644 www/modules/civicrm/release-notes/5.72.1.md create mode 100644 www/modules/civicrm/release-notes/5.72.2.md rename www/modules/civicrm/release-notes/{5.70.2.md => 5.72.3.md} (72%) create mode 100644 www/modules/civicrm/release-notes/5.73.0.md create mode 100644 www/modules/civicrm/release-notes/5.73.1.md create mode 100644 www/modules/civicrm/release-notes/5.73.2.md create mode 100644 www/modules/civicrm/release-notes/5.73.3.md create mode 100644 www/modules/civicrm/release-notes/5.73.4.md create mode 100644 www/modules/civicrm/release-notes/5.74.0.md create mode 100644 www/modules/civicrm/release-notes/5.74.1.md create mode 100644 www/modules/civicrm/release-notes/5.74.2.md create mode 100644 www/modules/civicrm/release-notes/5.74.3.md create mode 100644 www/modules/civicrm/release-notes/5.74.4.md create mode 100644 www/modules/civicrm/schema/ACL/ACL.entityType.php create mode 100644 www/modules/civicrm/schema/ACL/ACLCache.entityType.php create mode 100644 www/modules/civicrm/schema/ACL/ACLEntityRole.entityType.php create mode 100644 www/modules/civicrm/schema/Activity/Activity.entityType.php create mode 100644 www/modules/civicrm/schema/Activity/ActivityContact.entityType.php create mode 100644 www/modules/civicrm/schema/Batch/Batch.entityType.php create mode 100644 www/modules/civicrm/schema/Batch/EntityBatch.entityType.php create mode 100644 www/modules/civicrm/schema/Campaign/Campaign.entityType.php create mode 100644 www/modules/civicrm/schema/Campaign/CampaignGroup.entityType.php create mode 100644 www/modules/civicrm/schema/Campaign/Survey.entityType.php create mode 100644 www/modules/civicrm/schema/Case/Case.entityType.php create mode 100644 www/modules/civicrm/schema/Case/CaseActivity.entityType.php create mode 100644 www/modules/civicrm/schema/Case/CaseContact.entityType.php create mode 100644 www/modules/civicrm/schema/Case/CaseType.entityType.php create mode 100644 www/modules/civicrm/schema/Contact/ACLContactCache.entityType.php create mode 100644 www/modules/civicrm/schema/Contact/Contact.entityType.php create mode 100644 www/modules/civicrm/schema/Contact/ContactType.entityType.php create mode 100644 www/modules/civicrm/schema/Contact/DashboardContact.entityType.php create mode 100644 www/modules/civicrm/schema/Contact/Group.entityType.php create mode 100644 www/modules/civicrm/schema/Contact/GroupContact.entityType.php create mode 100644 www/modules/civicrm/schema/Contact/GroupContactCache.entityType.php create mode 100644 www/modules/civicrm/schema/Contact/GroupNesting.entityType.php create mode 100644 www/modules/civicrm/schema/Contact/GroupOrganization.entityType.php create mode 100644 www/modules/civicrm/schema/Contact/Relationship.entityType.php create mode 100644 www/modules/civicrm/schema/Contact/RelationshipCache.entityType.php create mode 100644 www/modules/civicrm/schema/Contact/RelationshipType.entityType.php create mode 100644 www/modules/civicrm/schema/Contact/SavedSearch.entityType.php create mode 100644 www/modules/civicrm/schema/Contact/SubscriptionHistory.entityType.php create mode 100644 www/modules/civicrm/schema/Contribute/Contribution.entityType.php create mode 100644 www/modules/civicrm/schema/Contribute/ContributionPage.entityType.php create mode 100644 www/modules/civicrm/schema/Contribute/ContributionProduct.entityType.php create mode 100644 www/modules/civicrm/schema/Contribute/ContributionRecur.entityType.php create mode 100644 www/modules/civicrm/schema/Contribute/ContributionSoft.entityType.php create mode 100644 www/modules/civicrm/schema/Contribute/Premium.entityType.php create mode 100644 www/modules/civicrm/schema/Contribute/PremiumsProduct.entityType.php create mode 100644 www/modules/civicrm/schema/Contribute/Product.entityType.php create mode 100644 www/modules/civicrm/schema/Contribute/Widget.entityType.php create mode 100644 www/modules/civicrm/schema/Core/ActionLog.entityType.php create mode 100644 www/modules/civicrm/schema/Core/ActionSchedule.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Address.entityType.php create mode 100644 www/modules/civicrm/schema/Core/AddressFormat.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Cache.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Component.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Country.entityType.php create mode 100644 www/modules/civicrm/schema/Core/County.entityType.php create mode 100644 www/modules/civicrm/schema/Core/CustomField.entityType.php create mode 100644 www/modules/civicrm/schema/Core/CustomGroup.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Dashboard.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Discount.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Domain.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Email.entityType.php create mode 100644 www/modules/civicrm/schema/Core/EntityFile.entityType.php create mode 100644 www/modules/civicrm/schema/Core/EntityTag.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Extension.entityType.php create mode 100644 www/modules/civicrm/schema/Core/File.entityType.php create mode 100644 www/modules/civicrm/schema/Core/IM.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Job.entityType.php create mode 100644 www/modules/civicrm/schema/Core/JobLog.entityType.php create mode 100644 www/modules/civicrm/schema/Core/LocBlock.entityType.php create mode 100644 www/modules/civicrm/schema/Core/LocationType.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Log.entityType.php create mode 100644 www/modules/civicrm/schema/Core/MailSettings.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Managed.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Mapping.entityType.php create mode 100644 www/modules/civicrm/schema/Core/MappingField.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Menu.entityType.php create mode 100644 www/modules/civicrm/schema/Core/MessageTemplate.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Navigation.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Note.entityType.php create mode 100644 www/modules/civicrm/schema/Core/OpenID.entityType.php create mode 100644 www/modules/civicrm/schema/Core/OptionGroup.entityType.php create mode 100644 www/modules/civicrm/schema/Core/OptionValue.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Phone.entityType.php create mode 100644 www/modules/civicrm/schema/Core/PreferencesDate.entityType.php create mode 100644 www/modules/civicrm/schema/Core/PrevNextCache.entityType.php create mode 100644 www/modules/civicrm/schema/Core/PrintLabel.entityType.php create mode 100644 www/modules/civicrm/schema/Core/RecurringEntity.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Setting.entityType.php create mode 100644 www/modules/civicrm/schema/Core/StateProvince.entityType.php create mode 100644 www/modules/civicrm/schema/Core/StatusPreference.entityType.php create mode 100644 www/modules/civicrm/schema/Core/SystemLog.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Tag.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Timezone.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Translation.entityType.php create mode 100644 www/modules/civicrm/schema/Core/UFField.entityType.php create mode 100644 www/modules/civicrm/schema/Core/UFGroup.entityType.php create mode 100644 www/modules/civicrm/schema/Core/UFJoin.entityType.php create mode 100644 www/modules/civicrm/schema/Core/UFMatch.entityType.php create mode 100644 www/modules/civicrm/schema/Core/UserJob.entityType.php create mode 100644 www/modules/civicrm/schema/Core/Website.entityType.php create mode 100644 www/modules/civicrm/schema/Core/WordReplacement.entityType.php create mode 100644 www/modules/civicrm/schema/Core/WorldRegion.entityType.php create mode 100644 www/modules/civicrm/schema/Cxn/Cxn.entityType.php create mode 100644 www/modules/civicrm/schema/Dedupe/DedupeException.entityType.php create mode 100644 www/modules/civicrm/schema/Dedupe/DedupeRule.entityType.php create mode 100644 www/modules/civicrm/schema/Dedupe/DedupeRuleGroup.entityType.php create mode 100644 www/modules/civicrm/schema/Event/Cart.entityType.php create mode 100644 www/modules/civicrm/schema/Event/Event.entityType.php create mode 100644 www/modules/civicrm/schema/Event/EventInCart.entityType.php create mode 100644 www/modules/civicrm/schema/Event/Participant.entityType.php create mode 100644 www/modules/civicrm/schema/Event/ParticipantPayment.entityType.php create mode 100644 www/modules/civicrm/schema/Event/ParticipantStatusType.entityType.php create mode 100644 www/modules/civicrm/schema/Financial/Currency.entityType.php create mode 100644 www/modules/civicrm/schema/Financial/EntityFinancialAccount.entityType.php create mode 100644 www/modules/civicrm/schema/Financial/EntityFinancialTrxn.entityType.php create mode 100644 www/modules/civicrm/schema/Financial/FinancialAccount.entityType.php create mode 100644 www/modules/civicrm/schema/Financial/FinancialItem.entityType.php create mode 100644 www/modules/civicrm/schema/Financial/FinancialTrxn.entityType.php create mode 100644 www/modules/civicrm/schema/Financial/FinancialType.entityType.php create mode 100644 www/modules/civicrm/schema/Financial/PaymentProcessor.entityType.php create mode 100644 www/modules/civicrm/schema/Financial/PaymentProcessorType.entityType.php create mode 100644 www/modules/civicrm/schema/Financial/PaymentToken.entityType.php create mode 100644 www/modules/civicrm/schema/Friend/Friend.entityType.php create mode 100644 www/modules/civicrm/schema/Mailing/BouncePattern.entityType.php create mode 100644 www/modules/civicrm/schema/Mailing/BounceType.entityType.php create mode 100644 www/modules/civicrm/schema/Mailing/Mailing.entityType.php create mode 100644 www/modules/civicrm/schema/Mailing/MailingAB.entityType.php create mode 100644 www/modules/civicrm/schema/Mailing/MailingComponent.entityType.php create mode 100644 www/modules/civicrm/schema/Mailing/MailingEventBounce.entityType.php create mode 100644 www/modules/civicrm/schema/Mailing/MailingEventConfirm.entityType.php create mode 100644 www/modules/civicrm/schema/Mailing/MailingEventDelivered.entityType.php create mode 100644 www/modules/civicrm/schema/Mailing/MailingEventForward.entityType.php create mode 100644 www/modules/civicrm/schema/Mailing/MailingEventOpened.entityType.php create mode 100644 www/modules/civicrm/schema/Mailing/MailingEventQueue.entityType.php create mode 100644 www/modules/civicrm/schema/Mailing/MailingEventReply.entityType.php create mode 100644 www/modules/civicrm/schema/Mailing/MailingEventSubscribe.entityType.php create mode 100644 www/modules/civicrm/schema/Mailing/MailingEventTrackableURLOpen.entityType.php create mode 100644 www/modules/civicrm/schema/Mailing/MailingEventUnsubscribe.entityType.php create mode 100644 www/modules/civicrm/schema/Mailing/MailingGroup.entityType.php create mode 100644 www/modules/civicrm/schema/Mailing/MailingJob.entityType.php create mode 100644 www/modules/civicrm/schema/Mailing/MailingRecipients.entityType.php create mode 100644 www/modules/civicrm/schema/Mailing/MailingTrackableURL.entityType.php create mode 100644 www/modules/civicrm/schema/Mailing/Spool.entityType.php create mode 100644 www/modules/civicrm/schema/Member/Membership.entityType.php create mode 100644 www/modules/civicrm/schema/Member/MembershipBlock.entityType.php create mode 100644 www/modules/civicrm/schema/Member/MembershipLog.entityType.php create mode 100644 www/modules/civicrm/schema/Member/MembershipPayment.entityType.php create mode 100644 www/modules/civicrm/schema/Member/MembershipStatus.entityType.php create mode 100644 www/modules/civicrm/schema/Member/MembershipType.entityType.php create mode 100644 www/modules/civicrm/schema/PCP/PCP.entityType.php create mode 100644 www/modules/civicrm/schema/PCP/PCPBlock.entityType.php create mode 100644 www/modules/civicrm/schema/Pledge/Pledge.entityType.php create mode 100644 www/modules/civicrm/schema/Pledge/PledgeBlock.entityType.php create mode 100644 www/modules/civicrm/schema/Pledge/PledgePayment.entityType.php create mode 100644 www/modules/civicrm/schema/Price/LineItem.entityType.php create mode 100644 www/modules/civicrm/schema/Price/PriceField.entityType.php create mode 100644 www/modules/civicrm/schema/Price/PriceFieldValue.entityType.php create mode 100644 www/modules/civicrm/schema/Price/PriceSet.entityType.php create mode 100644 www/modules/civicrm/schema/Price/PriceSetEntity.entityType.php create mode 100644 www/modules/civicrm/schema/Queue/Queue.entityType.php create mode 100644 www/modules/civicrm/schema/Queue/QueueItem.entityType.php create mode 100644 www/modules/civicrm/schema/Report/ReportInstance.entityType.php create mode 100644 www/modules/civicrm/schema/SMS/SmsProvider.entityType.php create mode 100644 www/modules/civicrm/setup/plugins/installFiles/StandaloneCivicrmFilesPath.civi-setup.php delete mode 100644 www/modules/civicrm/templates/CRM/Core/Form/RecurringEntity.hlp create mode 100644 www/modules/civicrm/vendor/civicrm/gitignore/LICENSE rename www/modules/civicrm/vendor/{togos => civicrm}/gitignore/Makefile (87%) create mode 100644 www/modules/civicrm/vendor/civicrm/gitignore/README.md rename www/modules/civicrm/vendor/{togos => civicrm}/gitignore/composer.json (83%) create mode 100644 www/modules/civicrm/vendor/civicrm/gitignore/shell.nix rename www/modules/civicrm/vendor/{togos => civicrm}/gitignore/src/main/php/TOGoS/GitIgnore/FileFinder.php (91%) rename www/modules/civicrm/vendor/{togos => civicrm}/gitignore/src/main/php/TOGoS/GitIgnore/Pattern.php (100%) rename www/modules/civicrm/vendor/{togos => civicrm}/gitignore/src/main/php/TOGoS/GitIgnore/Rule.php (63%) rename www/modules/civicrm/vendor/{togos => civicrm}/gitignore/src/main/php/TOGoS/GitIgnore/Ruleset.php (100%) rename www/modules/civicrm/vendor/{togos => civicrm}/gitignore/src/main/php/TOGoS/GitIgnore/Util.php (100%) create mode 100644 www/modules/civicrm/vendor/pear/db/DB/sqlite3.php delete mode 100644 www/modules/civicrm/vendor/pear/net_smtp/.github/workflows/ci.yml create mode 100644 www/modules/civicrm/vendor/pear/net_socket/.travis.yml create mode 100644 www/modules/civicrm/vendor/pear/net_socket/LICENSE create mode 100644 www/modules/civicrm/vendor/pear/net_socket/README.md create mode 100644 www/modules/civicrm/vendor/pear/net_socket/phpunit.xml.dist delete mode 100644 www/modules/civicrm/vendor/pontedilana/php-weasyprint/src/Image.php delete mode 100644 www/modules/civicrm/vendor/pontedilana/php-weasyprint/src/Version.php create mode 100644 www/modules/civicrm/vendor/sabberworm/php-css-parser/src/Parsing/Anchor.php diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/CommunicationsPreferences/Utils.php b/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/CommunicationsPreferences/Utils.php index 84eab41b2..0e119b8d1 100644 --- a/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/CommunicationsPreferences/Utils.php +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/CommunicationsPreferences/Utils.php @@ -520,12 +520,12 @@ public static function updateCommsPrefByFormValues($contactId, $submittedValues) ]); $existingPreferredMethod = $apiResult['preferred_communication_method']; - + if( !is_array( $existingPreferredMethod ) ){ // create an empty array to avoid error message $existingPreferredMethod = []; } - + $existingPreferredMethod = array_fill_keys($existingPreferredMethod, 1); } catch (Exception $e) { CRM_Core_Error::debug_var('updateCommsPrefByFormValues', $e); @@ -540,7 +540,7 @@ public static function updateCommsPrefByFormValues($contactId, $submittedValues) $name = str_replace($containerPrefix, '', $key); if (!empty($submittedValues[$key])) { $channelValue = $submittedValues[$key]; - $commPref = array_merge($commPref, $commPrefMapper[$name][$channelValue]); + $commPref = array_merge($commPref, ($commPrefMapper[$name][$channelValue] ?? [])); if ($name == 'post') { $name = 'postal mail'; diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Form/CommunicationsPreferences.php b/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Form/CommunicationsPreferences.php index 4eaf90904..7b0535289 100644 --- a/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Form/CommunicationsPreferences.php +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Form/CommunicationsPreferences.php @@ -312,7 +312,7 @@ public function setDefaultValues() { // If value is missing in the setting, take the corresponding value from the // group. foreach($map as $setting_key => $group_key) { - $item[$setting_key] = $item[$setting_key] ?? $grp['frontend_' . $group_key] ?? $grp[$group_key]; + $item[$setting_key] = $item[$setting_key] ?? $grp['frontend_' . $group_key] ?? $grp[$group_key] ?? ''; } // Set default weight. if (empty($item['group_weight'])) { diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Form/ContributionPage/TermsAndConditions.php b/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Form/ContributionPage/TermsAndConditions.php index 5f606cf8c..d1ef2524e 100644 --- a/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Form/ContributionPage/TermsAndConditions.php +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Form/ContributionPage/TermsAndConditions.php @@ -23,7 +23,7 @@ public function preProcess() { $this->_id = $this->id = $this->_entityId = CRM_Utils_Request::retrieve('id', 'Positive'); $group_id = $this->getGroupId(); if (method_exists('CRM_Custom_Form_CustomData', 'setGroupTree')) { - CRM_Custom_Form_CustomData::setGroupTree($this, '', $group_id, $this->_onlySubtype); + CRM_Custom_Form_CustomData::setGroupTree($this, '', $group_id); } else { // 4.6 @@ -37,7 +37,7 @@ public function preProcess() { $this->_groupTree = $formatted_tree; } } - + /** * Get the Id of the custom group for terms and conditions. */ @@ -52,7 +52,7 @@ private function getGroupId() { } return $this->groupId; } - + /** * Gets Custom field from the group tree by name. */ @@ -83,7 +83,7 @@ private function getFieldByName($field_name) { } return $fields[$field_name] ? $fields[$field_name] : []; } - + /** * Fetches a Custom Field definition from the API. */ @@ -153,10 +153,10 @@ public function buildQuickForm() { 'Terms & Conditions File' ); $tc_current = [ - 'url' => $tc_field['element_value'], - 'label' => basename($tc_field['element_value']), + 'url' => $tc_field['element_value'] ?? '', + 'label' => basename($tc_field['element_value'] ?? ''), ]; - $tc_value = $tc_field['element_value']; + $tc_value = $tc_field['element_value'] ?? ''; // Provide some variables so the template can display the upload field in // place of the link field. $this->assign('terms_conditions_link_element_name', $tc_field['element_name']); @@ -184,7 +184,7 @@ private function versionIs($version) { } public function postProcess() { $this->saveValues(); - + // Calling parent::endPostProcess() will not direct to the right url since // this form is not included in the ContributionPage State Machine. CRM_Core_Session::setStatus(E::ts("Terms & Conditions information has been saved."), E::ts('Saved'), 'success'); diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Form/ManageEvent/TermsAndConditions.php b/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Form/ManageEvent/TermsAndConditions.php index 12336111c..e77c26ae5 100644 --- a/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Form/ManageEvent/TermsAndConditions.php +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Form/ManageEvent/TermsAndConditions.php @@ -158,10 +158,10 @@ public function buildQuickForm() { 'Terms & Conditions File' ); $tc_current = [ - 'url' => $tc_field['element_value'], - 'label' => basename($tc_field['element_value']), + 'url' => $tc_field['element_value'] ?? '', + 'label' => basename($tc_field['element_value'] ?? ''), ]; - $tc_value = $tc_field['element_value']; + $tc_value = $tc_field['element_value'] ?? ''; // Provide some variables so the template can display the upload field in // place of the link field. $this->assign('terms_conditions_link_element_name', $tc_field['element_name']); diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Hook.php b/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Hook.php index 1374b209f..6ee17d9c7 100644 --- a/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Hook.php +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Hook.php @@ -41,7 +41,7 @@ class CRM_Gdpr_Hook { */ static function alterAnonymizeContactParams(&$params) { return CRM_Utils_Hook::singleton() - ->invoke(1, $params, CRM_Utils_Hook::$_nullObject, CRM_Utils_Hook::$_nullObject, CRM_Utils_Hook::$_nullObject, + ->invoke(['params'], $params, CRM_Utils_Hook::$_nullObject, CRM_Utils_Hook::$_nullObject, CRM_Utils_Hook::$_nullObject, CRM_Utils_Hook::$_nullObject, CRM_Utils_Hook::$_nullObject, 'civicrm_gdpr_alterAnonymizeContactParams'); } diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/SLA/Entity.php b/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/SLA/Entity.php index 5d4af3fd7..121223f7c 100644 --- a/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/SLA/Entity.php +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/SLA/Entity.php @@ -190,16 +190,16 @@ public function addField($form) { break; } } - - $text = $settings['entity_tc_checkbox_text']; - if (!empty($links['entity'])) { - $form->add('checkbox', 'accept_entity_tc', $text, [], TRUE); - } } } + if (!empty($links['entity'])) { + $text = $settings['entity_tc_checkbox_text']; + $form->add('checkbox', 'accept_entity_tc', $text, NULL, TRUE); + } + if (!empty($links['global'])) { - $form->add('checkbox', 'accept_tc', CRM_Gdpr_SLA_Utils::getCheckboxText(), [], TRUE); + $form->add('checkbox', 'accept_tc', CRM_Gdpr_SLA_Utils::getCheckboxText(), NULL, TRUE); } if (!empty($links)) { $tc_vars = [ diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Upgrader.php b/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Upgrader.php index d3863c740..14e7a9e73 100644 --- a/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Upgrader.php +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Upgrader.php @@ -4,7 +4,7 @@ /** * Collection of upgrade steps. */ -class CRM_Gdpr_Upgrader extends CRM_Gdpr_Upgrader_Base { +class CRM_Gdpr_Upgrader extends CRM_Extension_Upgrader_Base { // By convention, functions that look like "function upgrade_NNNN()" are // upgrade tasks. They are executed in order (like Drupal's hook_update_N). diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Upgrader/Base.php b/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Upgrader/Base.php deleted file mode 100644 index 457bb2751..000000000 --- a/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Upgrader/Base.php +++ /dev/null @@ -1,396 +0,0 @@ -ctx = array_shift($args); - $instance->queue = $instance->ctx->queue; - $method = array_shift($args); - return call_user_func_array([$instance, $method], $args); - } - - /** - * CRM_Gdpr_Upgrader_Base constructor. - * - * @param $extensionName - * @param $extensionDir - */ - public function __construct($extensionName, $extensionDir) { - $this->extensionName = $extensionName; - $this->extensionDir = $extensionDir; - } - - // ******** Task helpers ******** - - /** - * Run a CustomData file. - * - * @param string $relativePath - * the CustomData XML file path (relative to this extension's dir) - * @return bool - */ - public function executeCustomDataFile($relativePath) { - $xml_file = $this->extensionDir . '/' . $relativePath; - return $this->executeCustomDataFileByAbsPath($xml_file); - } - - /** - * Run a CustomData file - * - * @param string $xml_file - * the CustomData XML file path (absolute path) - * - * @return bool - */ - protected function executeCustomDataFileByAbsPath($xml_file) { - $import = new CRM_Utils_Migrate_Import(); - $import->run($xml_file); - return TRUE; - } - - /** - * Run a SQL file. - * - * @param string $relativePath - * the SQL file path (relative to this extension's dir) - * - * @return bool - */ - public function executeSqlFile($relativePath) { - CRM_Utils_File::sourceSQLFile( - CIVICRM_DSN, - $this->extensionDir . DIRECTORY_SEPARATOR . $relativePath - ); - return TRUE; - } - - /** - * Run the sql commands in the specified file. - * - * @param string $tplFile - * The SQL file path (relative to this extension's dir). - * Ex: "sql/mydata.mysql.tpl". - * - * @return bool - * @throws \CRM_Core_Exception - */ - public function executeSqlTemplate($tplFile) { - // Assign multilingual variable to Smarty. - $upgrade = new CRM_Upgrade_Form(); - - $tplFile = CRM_Utils_File::isAbsolute($tplFile) ? $tplFile : $this->extensionDir . DIRECTORY_SEPARATOR . $tplFile; - $smarty = CRM_Core_Smarty::singleton(); - $smarty->assign('domainID', CRM_Core_Config::domainID()); - CRM_Utils_File::sourceSQLFile( - CIVICRM_DSN, $smarty->fetch($tplFile), NULL, TRUE - ); - return TRUE; - } - - /** - * Run one SQL query. - * - * This is just a wrapper for CRM_Core_DAO::executeSql, but it - * provides syntactic sugar for queueing several tasks that - * run different queries - * - * @return bool - */ - public function executeSql($query, $params = []) { - // FIXME verify that we raise an exception on error - CRM_Core_DAO::executeQuery($query, $params); - return TRUE; - } - - /** - * Syntactic sugar for enqueuing a task which calls a function in this class. - * - * The task is weighted so that it is processed - * as part of the currently-pending revision. - * - * After passing the $funcName, you can also pass parameters that will go to - * the function. Note that all params must be serializable. - */ - public function addTask($title) { - $args = func_get_args(); - $title = array_shift($args); - $task = new CRM_Queue_Task( - [get_class($this), '_queueAdapter'], - $args, - $title - ); - return $this->queue->createItem($task, ['weight' => -1]); - } - - // ******** Revision-tracking helpers ******** - - /** - * Determine if there are any pending revisions. - * - * @return bool - */ - public function hasPendingRevisions() { - $revisions = $this->getRevisions(); - $currentRevision = $this->getCurrentRevision(); - - if (empty($revisions)) { - return FALSE; - } - if (empty($currentRevision)) { - return TRUE; - } - - return ($currentRevision < max($revisions)); - } - - /** - * Add any pending revisions to the queue. - * - * @param CRM_Queue_Queue $queue - */ - public function enqueuePendingRevisions(CRM_Queue_Queue $queue) { - $this->queue = $queue; - - $currentRevision = $this->getCurrentRevision(); - foreach ($this->getRevisions() as $revision) { - if ($revision > $currentRevision) { - $title = E::ts('Upgrade %1 to revision %2', [ - 1 => $this->extensionName, - 2 => $revision, - ]); - - // note: don't use addTask() because it sets weight=-1 - - $task = new CRM_Queue_Task( - [get_class($this), '_queueAdapter'], - ['upgrade_' . $revision], - $title - ); - $this->queue->createItem($task); - - $task = new CRM_Queue_Task( - [get_class($this), '_queueAdapter'], - ['setCurrentRevision', $revision], - $title - ); - $this->queue->createItem($task); - } - } - } - - /** - * Get a list of revisions. - * - * @return array - * revisionNumbers sorted numerically - */ - public function getRevisions() { - if (!is_array($this->revisions)) { - $this->revisions = []; - - $clazz = new ReflectionClass(get_class($this)); - $methods = $clazz->getMethods(); - foreach ($methods as $method) { - if (preg_match('/^upgrade_(.*)/', $method->name, $matches)) { - $this->revisions[] = $matches[1]; - } - } - sort($this->revisions, SORT_NUMERIC); - } - - return $this->revisions; - } - - public function getCurrentRevision() { - $revision = CRM_Core_BAO_Extension::getSchemaVersion($this->extensionName); - if (!$revision) { - $revision = $this->getCurrentRevisionDeprecated(); - } - return $revision; - } - - private function getCurrentRevisionDeprecated() { - $key = $this->extensionName . ':version'; - if ($revision = \Civi::settings()->get($key)) { - $this->revisionStorageIsDeprecated = TRUE; - } - return $revision; - } - - public function setCurrentRevision($revision) { - CRM_Core_BAO_Extension::setSchemaVersion($this->extensionName, $revision); - // clean up legacy schema version store (CRM-19252) - $this->deleteDeprecatedRevision(); - return TRUE; - } - - private function deleteDeprecatedRevision() { - if ($this->revisionStorageIsDeprecated) { - $setting = new CRM_Core_BAO_Setting(); - $setting->name = $this->extensionName . ':version'; - $setting->delete(); - CRM_Core_Error::debug_log_message("Migrated extension schema revision ID for {$this->extensionName} from civicrm_setting (deprecated) to civicrm_extension.\n"); - } - } - - // ******** Hook delegates ******** - - /** - * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_install - */ - public function onInstall() { - $files = glob($this->extensionDir . '/sql/*_install.sql'); - if (is_array($files)) { - foreach ($files as $file) { - CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file); - } - } - $files = glob($this->extensionDir . '/sql/*_install.mysql.tpl'); - if (is_array($files)) { - foreach ($files as $file) { - $this->executeSqlTemplate($file); - } - } - $files = glob($this->extensionDir . '/xml/*_install.xml'); - if (is_array($files)) { - foreach ($files as $file) { - $this->executeCustomDataFileByAbsPath($file); - } - } - if (is_callable([$this, 'install'])) { - $this->install(); - } - } - - /** - * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_postInstall - */ - public function onPostInstall() { - $revisions = $this->getRevisions(); - if (!empty($revisions)) { - $this->setCurrentRevision(max($revisions)); - } - if (is_callable([$this, 'postInstall'])) { - $this->postInstall(); - } - } - - /** - * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_uninstall - */ - public function onUninstall() { - $files = glob($this->extensionDir . '/sql/*_uninstall.mysql.tpl'); - if (is_array($files)) { - foreach ($files as $file) { - $this->executeSqlTemplate($file); - } - } - if (is_callable([$this, 'uninstall'])) { - $this->uninstall(); - } - $files = glob($this->extensionDir . '/sql/*_uninstall.sql'); - if (is_array($files)) { - foreach ($files as $file) { - CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file); - } - } - } - - /** - * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_enable - */ - public function onEnable() { - // stub for possible future use - if (is_callable([$this, 'enable'])) { - $this->enable(); - } - } - - /** - * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_disable - */ - public function onDisable() { - // stub for possible future use - if (is_callable([$this, 'disable'])) { - $this->disable(); - } - } - - public function onUpgrade($op, CRM_Queue_Queue $queue = NULL) { - switch ($op) { - case 'check': - return [$this->hasPendingRevisions()]; - - case 'enqueue': - return $this->enqueuePendingRevisions($queue); - - default: - } - } - -} diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Utils.php b/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Utils.php index a37ab56a4..4867403d4 100644 --- a/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Utils.php +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/CRM/Gdpr/Utils.php @@ -781,7 +781,6 @@ public static function setItem($value, $group, $name) { } else { CRM_Core_BAO_Setting::setItem($value, $group, $name); } - return $settingValue; } }//End Class diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/composer.json b/www/civi-extensions/uk.co.vedaconsulting.gdpr/composer.json new file mode 100644 index 000000000..780089939 --- /dev/null +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/composer.json @@ -0,0 +1,5 @@ +{ + "name": "civicrm/gdpr", + "type": "civicrm-ext", + "description": "General Data Protection Regulation" +} diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/docs/hooks.md b/www/civi-extensions/uk.co.vedaconsulting.gdpr/docs/hooks.md new file mode 100644 index 000000000..c0061e085 --- /dev/null +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/docs/hooks.md @@ -0,0 +1,10 @@ +# Hooks + +The following hooks are made available... + +* `hook_civicrm_gdpr_alterAnonymizeContactParams( &$params )` + + This hook allows to alter contact parameters when anonymizing contact. + + `$params` - Parameters for the anonymous contact. + diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/gdpr.civix.php b/www/civi-extensions/uk.co.vedaconsulting.gdpr/gdpr.civix.php index 24ee691f4..778662807 100644 --- a/www/civi-extensions/uk.co.vedaconsulting.gdpr/gdpr.civix.php +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/gdpr.civix.php @@ -24,7 +24,7 @@ class CRM_Gdpr_ExtensionUtil { * Translated text. * @see ts */ - public static function ts($text, $params = []) { + public static function ts($text, $params = []): string { if (!array_key_exists('domain', $params)) { $params['domain'] = [self::LONG_NAME, NULL]; } @@ -41,7 +41,7 @@ public static function ts($text, $params = []) { * Ex: 'http://example.org/sites/default/ext/org.example.foo'. * Ex: 'http://example.org/sites/default/ext/org.example.foo/css/foo.css'. */ - public static function url($file = NULL) { + public static function url($file = NULL): string { if ($file === NULL) { return rtrim(CRM_Core_Resources::singleton()->getUrl(self::LONG_NAME), '/'); } @@ -79,45 +79,29 @@ public static function findClass($suffix) { use CRM_Gdpr_ExtensionUtil as E; +function _gdpr_civix_mixin_polyfill() { + if (!class_exists('CRM_Extension_MixInfo')) { + $polyfill = __DIR__ . '/mixin/polyfill.php'; + (require $polyfill)(E::LONG_NAME, E::SHORT_NAME, E::path()); + } +} + /** * (Delegated) Implements hook_civicrm_config(). * * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_config */ -function _gdpr_civix_civicrm_config(&$config = NULL) { +function _gdpr_civix_civicrm_config($config = NULL) { static $configured = FALSE; if ($configured) { return; } $configured = TRUE; - $template =& CRM_Core_Smarty::singleton(); - - $extRoot = dirname(__FILE__) . DIRECTORY_SEPARATOR; - $extDir = $extRoot . 'templates'; - - if (is_array($template->template_dir)) { - array_unshift($template->template_dir, $extDir); - } - else { - $template->template_dir = [$extDir, $template->template_dir]; - } - + $extRoot = __DIR__ . DIRECTORY_SEPARATOR; $include_path = $extRoot . PATH_SEPARATOR . get_include_path(); set_include_path($include_path); -} - -/** - * (Delegated) Implements hook_civicrm_xmlMenu(). - * - * @param $files array(string) - * - * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_xmlMenu - */ -function _gdpr_civix_civicrm_xmlMenu(&$files) { - foreach (_gdpr_civix_glob(__DIR__ . '/xml/Menu/*.xml') as $file) { - $files[] = $file; - } + _gdpr_civix_mixin_polyfill(); } /** @@ -127,35 +111,7 @@ function _gdpr_civix_civicrm_xmlMenu(&$files) { */ function _gdpr_civix_civicrm_install() { _gdpr_civix_civicrm_config(); - if ($upgrader = _gdpr_civix_upgrader()) { - $upgrader->onInstall(); - } -} - -/** - * Implements hook_civicrm_postInstall(). - * - * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_postInstall - */ -function _gdpr_civix_civicrm_postInstall() { - _gdpr_civix_civicrm_config(); - if ($upgrader = _gdpr_civix_upgrader()) { - if (is_callable([$upgrader, 'onPostInstall'])) { - $upgrader->onPostInstall(); - } - } -} - -/** - * Implements hook_civicrm_uninstall(). - * - * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_uninstall - */ -function _gdpr_civix_civicrm_uninstall() { - _gdpr_civix_civicrm_config(); - if ($upgrader = _gdpr_civix_upgrader()) { - $upgrader->onUninstall(); - } + _gdpr_civix_mixin_polyfill(); } /** @@ -163,212 +119,9 @@ function _gdpr_civix_civicrm_uninstall() { * * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_enable */ -function _gdpr_civix_civicrm_enable() { - _gdpr_civix_civicrm_config(); - if ($upgrader = _gdpr_civix_upgrader()) { - if (is_callable([$upgrader, 'onEnable'])) { - $upgrader->onEnable(); - } - } -} - -/** - * (Delegated) Implements hook_civicrm_disable(). - * - * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_disable - * @return mixed - */ -function _gdpr_civix_civicrm_disable() { +function _gdpr_civix_civicrm_enable(): void { _gdpr_civix_civicrm_config(); - if ($upgrader = _gdpr_civix_upgrader()) { - if (is_callable([$upgrader, 'onDisable'])) { - $upgrader->onDisable(); - } - } -} - -/** - * (Delegated) Implements hook_civicrm_upgrade(). - * - * @param $op string, the type of operation being performed; 'check' or 'enqueue' - * @param $queue CRM_Queue_Queue, (for 'enqueue') the modifiable list of pending up upgrade tasks - * - * @return mixed - * based on op. for 'check', returns array(boolean) (TRUE if upgrades are pending) - * for 'enqueue', returns void - * - * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_upgrade - */ -function _gdpr_civix_civicrm_upgrade($op, CRM_Queue_Queue $queue = NULL) { - if ($upgrader = _gdpr_civix_upgrader()) { - return $upgrader->onUpgrade($op, $queue); - } -} - -/** - * @return CRM_Gdpr_Upgrader - */ -function _gdpr_civix_upgrader() { - if (!file_exists(__DIR__ . '/CRM/Gdpr/Upgrader.php')) { - return NULL; - } - else { - return CRM_Gdpr_Upgrader_Base::instance(); - } -} - -/** - * Search directory tree for files which match a glob pattern. - * - * Note: Dot-directories (like "..", ".git", or ".svn") will be ignored. - * Note: In Civi 4.3+, delegate to CRM_Utils_File::findFiles() - * - * @param string $dir base dir - * @param string $pattern , glob pattern, eg "*.txt" - * - * @return array - */ -function _gdpr_civix_find_files($dir, $pattern) { - if (is_callable(['CRM_Utils_File', 'findFiles'])) { - return CRM_Utils_File::findFiles($dir, $pattern); - } - - $todos = [$dir]; - $result = []; - while (!empty($todos)) { - $subdir = array_shift($todos); - foreach (_gdpr_civix_glob("$subdir/$pattern") as $match) { - if (!is_dir($match)) { - $result[] = $match; - } - } - if ($dh = opendir($subdir)) { - while (FALSE !== ($entry = readdir($dh))) { - $path = $subdir . DIRECTORY_SEPARATOR . $entry; - if ($entry[0] == '.') { - } - elseif (is_dir($path)) { - $todos[] = $path; - } - } - closedir($dh); - } - } - return $result; -} - -/** - * (Delegated) Implements hook_civicrm_managed(). - * - * Find any *.mgd.php files, merge their content, and return. - * - * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_managed - */ -function _gdpr_civix_civicrm_managed(&$entities) { - $mgdFiles = _gdpr_civix_find_files(__DIR__, '*.mgd.php'); - sort($mgdFiles); - foreach ($mgdFiles as $file) { - $es = include $file; - foreach ($es as $e) { - if (empty($e['module'])) { - $e['module'] = E::LONG_NAME; - } - if (empty($e['params']['version'])) { - $e['params']['version'] = '3'; - } - $entities[] = $e; - } - } -} - -/** - * (Delegated) Implements hook_civicrm_caseTypes(). - * - * Find any and return any files matching "xml/case/*.xml" - * - * Note: This hook only runs in CiviCRM 4.4+. - * - * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_caseTypes - */ -function _gdpr_civix_civicrm_caseTypes(&$caseTypes) { - if (!is_dir(__DIR__ . '/xml/case')) { - return; - } - - foreach (_gdpr_civix_glob(__DIR__ . '/xml/case/*.xml') as $file) { - $name = preg_replace('/\.xml$/', '', basename($file)); - if ($name != CRM_Case_XMLProcessor::mungeCaseType($name)) { - $errorMessage = sprintf("Case-type file name is malformed (%s vs %s)", $name, CRM_Case_XMLProcessor::mungeCaseType($name)); - throw new CRM_Core_Exception($errorMessage); - } - $caseTypes[$name] = [ - 'module' => E::LONG_NAME, - 'name' => $name, - 'file' => $file, - ]; - } -} - -/** - * (Delegated) Implements hook_civicrm_angularModules(). - * - * Find any and return any files matching "ang/*.ang.php" - * - * Note: This hook only runs in CiviCRM 4.5+. - * - * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_angularModules - */ -function _gdpr_civix_civicrm_angularModules(&$angularModules) { - if (!is_dir(__DIR__ . '/ang')) { - return; - } - - $files = _gdpr_civix_glob(__DIR__ . '/ang/*.ang.php'); - foreach ($files as $file) { - $name = preg_replace(':\.ang\.php$:', '', basename($file)); - $module = include $file; - if (empty($module['ext'])) { - $module['ext'] = E::LONG_NAME; - } - $angularModules[$name] = $module; - } -} - -/** - * (Delegated) Implements hook_civicrm_themes(). - * - * Find any and return any files matching "*.theme.php" - */ -function _gdpr_civix_civicrm_themes(&$themes) { - $files = _gdpr_civix_glob(__DIR__ . '/*.theme.php'); - foreach ($files as $file) { - $themeMeta = include $file; - if (empty($themeMeta['name'])) { - $themeMeta['name'] = preg_replace(':\.theme\.php$:', '', basename($file)); - } - if (empty($themeMeta['ext'])) { - $themeMeta['ext'] = E::LONG_NAME; - } - $themes[$themeMeta['name']] = $themeMeta; - } -} - -/** - * Glob wrapper which is guaranteed to return an array. - * - * The documentation for glob() says, "On some systems it is impossible to - * distinguish between empty match and an error." Anecdotally, the return - * result for an empty match is sometimes array() and sometimes FALSE. - * This wrapper provides consistency. - * - * @link http://php.net/glob - * @param string $pattern - * - * @return array - */ -function _gdpr_civix_glob($pattern) { - $result = glob($pattern); - return is_array($result) ? $result : []; + _gdpr_civix_mixin_polyfill(); } /** @@ -387,8 +140,8 @@ function _gdpr_civix_insert_navigation_menu(&$menu, $path, $item) { if (empty($path)) { $menu[] = [ 'attributes' => array_merge([ - 'label' => CRM_Utils_Array::value('name', $item), - 'active' => 1, + 'label' => $item['name'] ?? NULL, + 'active' => 1, ], $item), ]; return TRUE; @@ -452,26 +205,3 @@ function _gdpr_civix_fixNavigationMenuItems(&$nodes, &$maxNavID, $parentID) { } } } - -/** - * (Delegated) Implements hook_civicrm_alterSettingsFolders(). - * - * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_alterSettingsFolders - */ -function _gdpr_civix_civicrm_alterSettingsFolders(&$metaDataFolders = NULL) { - $settingsDir = __DIR__ . DIRECTORY_SEPARATOR . 'settings'; - if (!in_array($settingsDir, $metaDataFolders) && is_dir($settingsDir)) { - $metaDataFolders[] = $settingsDir; - } -} - -/** - * (Delegated) Implements hook_civicrm_entityTypes(). - * - * Find any *.entityType.php files, merge their content, and return. - * - * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_entityTypes - */ -function _gdpr_civix_civicrm_entityTypes(&$entityTypes) { - $entityTypes = array_merge($entityTypes, []); -} diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/gdpr.php b/www/civi-extensions/uk.co.vedaconsulting.gdpr/gdpr.php index 518ffe701..5f8f1d8df 100644 --- a/www/civi-extensions/uk.co.vedaconsulting.gdpr/gdpr.php +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/gdpr.php @@ -12,16 +12,6 @@ function gdpr_civicrm_config(&$config) { _gdpr_civix_civicrm_config($config); } - -/** - * Implements hook_civicrm_xmlMenu(). - * - * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_xmlMenu - */ -function gdpr_civicrm_xmlMenu(&$files) { - _gdpr_civix_civicrm_xmlMenu($files); -} - /** * Implements hook_civicrm_install(). * @@ -46,15 +36,6 @@ function gdpr_civicrm_install() { _gdpr_civix_civicrm_install(); } -/** - * Implements hook_civicrm_postInstall(). - * - * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_postInstall - */ -function gdpr_civicrm_postInstall() { - _gdpr_civix_civicrm_postInstall(); -} - /** * Implements hook_civicrm_uninstall(). * @@ -77,7 +58,6 @@ function gdpr_civicrm_uninstall() { ]); } } - _gdpr_civix_civicrm_uninstall(); } /** @@ -89,73 +69,6 @@ function gdpr_civicrm_enable() { _gdpr_civix_civicrm_enable(); } -/** - * Implements hook_civicrm_disable(). - * - * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_disable - */ -function gdpr_civicrm_disable() { - _gdpr_civix_civicrm_disable(); -} - -/** - * Implements hook_civicrm_upgrade(). - * - * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_upgrade - */ -function gdpr_civicrm_upgrade($op, CRM_Queue_Queue $queue = NULL) { - return _gdpr_civix_civicrm_upgrade($op, $queue); -} - -/** - * Implements hook_civicrm_managed(). - * - * Generate a list of entities to create/deactivate/delete when this module - * is installed, disabled, uninstalled. - * - * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_managed - */ -function gdpr_civicrm_managed(&$entities) { - _gdpr_civix_civicrm_managed($entities); -} - -/** - * Implements hook_civicrm_caseTypes(). - * - * Generate a list of case-types. - * - * Note: This hook only runs in CiviCRM 4.4+. - * - * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_caseTypes - */ -function gdpr_civicrm_caseTypes(&$caseTypes) { - _gdpr_civix_civicrm_caseTypes($caseTypes); -} - -/** - * Implements hook_civicrm_angularModules(). - * - * Generate a list of Angular modules. - * - * Note: This hook only runs in CiviCRM 4.5+. It may - * use features only available in v4.6+. - * - * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_angularModules - */ -function gdpr_civicrm_angularModules(&$angularModules) { - _gdpr_civix_civicrm_angularModules($angularModules); -} - -/** - * Implements hook_civicrm_alterSettingsFolders(). - * - * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_alterSettingsFolders - */ -function gdpr_civicrm_alterSettingsFolders(&$metaDataFolders = NULL) { - _gdpr_civix_civicrm_alterSettingsFolders($metaDataFolders); -} - - /** * Implementation of hook_civicrm_alterContent * @@ -334,7 +247,6 @@ function gdpr_civicrm_postProcess($formName, $form) { } } - /** * Implements hook_civicrm_post(). */ @@ -372,7 +284,6 @@ function gdpr_civicrm_tabset($tabsetName, &$tabs, $context) { } } - /** * Implements hook_civicrm_export(). */ @@ -578,16 +489,16 @@ function gdpr_civicrm_permission(&$permissions) { $prefix = E::ts('CiviGDPR') . ': '; $permissions += [ 'access GDPR' => [ - $prefix . E::ts('access GDPR'), - E::ts('View GDPR related information'), + 'label' => $prefix . E::ts('access GDPR'), + 'description' => E::ts('View GDPR related information'), ], 'forget contact' => [ - $prefix . E::ts('forget contact'), - E::ts('Anonymize contacts'), + 'label' => $prefix . E::ts('forget contact'), + 'description' => E::ts('Anonymize contacts'), ], 'administer GDPR' => [ - $prefix . E::ts('administer GDPR'), - E::ts('Manage GDPR settings'), + 'label' => $prefix . E::ts('administer GDPR'), + 'description' => E::ts('Manage GDPR settings'), ], ]; } diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/info.xml b/www/civi-extensions/uk.co.vedaconsulting.gdpr/info.xml index c713c351a..1e9c27526 100644 --- a/www/civi-extensions/uk.co.vedaconsulting.gdpr/info.xml +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/info.xml @@ -9,18 +9,29 @@ support@vedaconsulting.co.uk - https://github.com/veda-consulting/uk.co.vedaconsulting.gdpr + https://lab.civicrm.org/extensions/gdpr https://docs.civicrm.org/gdpr/en/latest/ https://vedaconsulting.co.uk http://www.gnu.org/licenses/agpl-3.0.html - 2021-10-24 - 3.5 + 2024-04-18 + 3.6 stable - 5.22 + 5.69 CRM/Gdpr + 23.02.1 + + menu-xml@1.0.0 + mgd-php@1.0.0 + smarty-v2@1.0.1 + + + + + + CRM_Gdpr_Upgrader diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/mixin/menu-xml@1.0.0.mixin.php b/www/civi-extensions/uk.co.vedaconsulting.gdpr/mixin/menu-xml@1.0.0.mixin.php new file mode 100644 index 000000000..4c0b2276c --- /dev/null +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/mixin/menu-xml@1.0.0.mixin.php @@ -0,0 +1,31 @@ +addListener('hook_civicrm_xmlMenu', function ($e) use ($mixInfo) { + if (!$mixInfo->isActive()) { + return; + } + + $files = (array) glob($mixInfo->getPath('xml/Menu/*.xml')); + foreach ($files as $file) { + $e->files[] = $file; + } + }); + +}; diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/mixin/mgd-php@1.0.0.mixin.php b/www/civi-extensions/uk.co.vedaconsulting.gdpr/mixin/mgd-php@1.0.0.mixin.php new file mode 100644 index 000000000..39d45b14a --- /dev/null +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/mixin/mgd-php@1.0.0.mixin.php @@ -0,0 +1,42 @@ +addListener('hook_civicrm_managed', function ($event) use ($mixInfo) { + // When deactivating on a polyfill/pre-mixin system, listeners may not cleanup automatically. + if (!$mixInfo->isActive()) { + return; + } + + $mgdFiles = CRM_Utils_File::findFiles($mixInfo->getPath(), '*.mgd.php'); + sort($mgdFiles); + foreach ($mgdFiles as $file) { + $es = include $file; + foreach ($es as $e) { + if (empty($e['module'])) { + $e['module'] = $mixInfo->longName; + } + if (empty($e['params']['version'])) { + $e['params']['version'] = '3'; + } + $event->entities[] = $e; + } + } + }); + +}; diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/mixin/polyfill.php b/www/civi-extensions/uk.co.vedaconsulting.gdpr/mixin/polyfill.php new file mode 100644 index 000000000..17ba1df3b --- /dev/null +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/mixin/polyfill.php @@ -0,0 +1,101 @@ +')) { + $mixinVers[$name] = $ver; + } + } + $mixins = []; + foreach ($mixinVers as $name => $ver) { + $mixins[] = "$name@$ver"; + } + + // Imitate CRM_Extension_MixInfo. + $mixInfo = new class() { + + /** + * @var string + */ + public $longName; + + /** + * @var string + */ + public $shortName; + + public $_basePath; + + public function getPath($file = NULL) { + return $this->_basePath . ($file === NULL ? '' : (DIRECTORY_SEPARATOR . $file)); + } + + public function isActive() { + return \CRM_Extension_System::singleton()->getMapper()->isActiveModule($this->shortName); + } + + }; + $mixInfo->longName = $longName; + $mixInfo->shortName = $shortName; + $mixInfo->_basePath = $basePath; + + // Imitate CRM_Extension_BootCache. + $bootCache = new class() { + + public function define($name, $callback) { + $envId = \CRM_Core_Config_Runtime::getId(); + $oldExtCachePath = \Civi::paths()->getPath("[civicrm.compile]/CachedExtLoader.{$envId}.php"); + $stat = stat($oldExtCachePath); + $file = Civi::paths()->getPath('[civicrm.compile]/CachedMixin.' . md5($name . ($stat['mtime'] ?? 0)) . '.php'); + if (file_exists($file)) { + return include $file; + } + else { + $data = $callback(); + file_put_contents($file, '<' . "?php\nreturn " . var_export($data, 1) . ';'); + return $data; + } + } + + }; + + // Imitate CRM_Extension_MixinLoader::run() + // Parse all live mixins before trying to scan any classes. + global $_CIVIX_MIXIN_POLYFILL; + foreach ($mixins as $mixin) { + // If the exact same mixin is defined by multiple exts, just use the first one. + if (!isset($_CIVIX_MIXIN_POLYFILL[$mixin])) { + $_CIVIX_MIXIN_POLYFILL[$mixin] = include_once $basePath . '/mixin/' . $mixin . '.mixin.php'; + } + } + foreach ($mixins as $mixin) { + // If there's trickery about installs/uninstalls/resets, then we may need to register a second time. + if (!isset(\Civi::$statics[$longName][$mixin])) { + \Civi::$statics[$longName][$mixin] = 1; + $func = $_CIVIX_MIXIN_POLYFILL[$mixin]; + $func($mixInfo, $bootCache); + } + } +}; diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/mixin/smarty-v2@1.0.1.mixin.php b/www/civi-extensions/uk.co.vedaconsulting.gdpr/mixin/smarty-v2@1.0.1.mixin.php new file mode 100644 index 000000000..5972dbdc5 --- /dev/null +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/mixin/smarty-v2@1.0.1.mixin.php @@ -0,0 +1,51 @@ +getPath('templates'); + if (!file_exists($dir)) { + return; + } + + $register = function() use ($dir) { + // This implementation has a theoretical edge-case bug on older versions of CiviCRM where a template could + // be registered more than once. + CRM_Core_Smarty::singleton()->addTemplateDir($dir); + }; + + // Let's figure out what environment we're in -- so that we know the best way to call $register(). + + if (!empty($GLOBALS['_CIVIX_MIXIN_POLYFILL'])) { + // Polyfill Loader (v<=5.45): We're already in the middle of firing `hook_config`. + if ($mixInfo->isActive()) { + $register(); + } + return; + } + + if (CRM_Extension_System::singleton()->getManager()->extensionIsBeingInstalledOrEnabled($mixInfo->longName)) { + // New Install, Standard Loader: The extension has just been enabled, and we're now setting it up. + // System has already booted. New templates may be needed for upcoming installation steps. + $register(); + return; + } + + // Typical Pageview, Standard Loader: Defer the actual registration for a moment -- to ensure that Smarty is online. + \Civi::dispatcher()->addListener('hook_civicrm_config', function() use ($mixInfo, $register) { + if ($mixInfo->isActive()) { + $register(); + } + }); + +}; diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/package-lock.json b/www/civi-extensions/uk.co.vedaconsulting.gdpr/package-lock.json index 198c279af..2a26fc6dc 100644 --- a/www/civi-extensions/uk.co.vedaconsulting.gdpr/package-lock.json +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/package-lock.json @@ -15,10 +15,10 @@ "husky": "^6.0.0", "lint-staged": "^11.0.0", "sass": "^1.35.1", - "stylelint": "^13.13.1", - "stylelint-config-sass-guidelines": "^8.0.0", - "stylelint-order": "^4.1.0", - "stylelint-scss": "^3.19.0" + "stylelint": "^15.10.3", + "stylelint-config-sass-guidelines": "^10.0.0", + "stylelint-order": "^6.0.3", + "stylelint-scss": "^5.2.1" }, "engines": { "node": ">=12", @@ -37,227 +37,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz", - "integrity": "sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.6.tgz", - "integrity": "sha512-gJnOEWSqTk96qG5BoIrl5bVtc23DCycmIePPYnamY9RboYdI4nFy5vAQMSl81O5K/W0sLDWfGysnOECC+KUUCA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", - "@babel/helper-compilation-targets": "^7.14.5", - "@babel/helper-module-transforms": "^7.14.5", - "@babel/helpers": "^7.14.6", - "@babel/parser": "^7.14.6", - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.14.5", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz", - "integrity": "sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.14.5", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", - "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", - "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", - "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz", - "integrity": "sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", - "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", - "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.14.5", - "@babel/helper-replace-supers": "^7.14.5", - "@babel/helper-simple-access": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", - "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", - "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.14.5", - "@babel/helper-optimise-call-expression": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.5.tgz", - "integrity": "sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", - "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-validator-identifier": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", @@ -267,29 +46,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.6.tgz", - "integrity": "sha512-yesp1ENQBiLI+iYHSJdoZKUtRpfTlL1grDIX9NRlAVppljLw/4tTyYupIB7uIYmC3stW/imAv8EqaKaS/ibmeA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/highlight": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", @@ -366,63 +122,90 @@ "node": ">=4" } }, - "node_modules/@babel/parser": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz", - "integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==", + "node_modules/@csstools/css-parser-algorithms": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.2.tgz", + "integrity": "sha512-sLYGdAdEY2x7TSw9FtmdaTrh2wFtRJO5VMbBrA8tEqEod7GEggFmxTSK9XqExib3yMuYNcvcTdCZIP6ukdjAIA==", "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { - "node": ">=6.0.0" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^2.2.1" } }, - "node_modules/@babel/template": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", - "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "node_modules/@csstools/css-tokenizer": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.2.1.tgz", + "integrity": "sha512-Zmsf2f/CaEPWEVgw29odOj+WEVoiJy9s9NOv5GgNY9mZ1CZ7394By6wONrONrTsnNDv6F9hR02nvFihrGVGHBg==", "dev": true, - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" } }, - "node_modules/@babel/traverse": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz", - "integrity": "sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ==", + "node_modules/@csstools/media-query-list-parser": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.5.tgz", + "integrity": "sha512-IxVBdYzR8pYe89JiyXQuYk4aVVoCPhMJkz6ElRwlVysjwURTsTk/bmY/z4FfeRE+CRBMlykPwXEVUg8lThv7AQ==", "dev": true, - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-hoist-variables": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.14.7", - "@babel/types": "^7.14.5", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^2.3.2", + "@csstools/css-tokenizer": "^2.2.1" } }, - "node_modules/@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "node_modules/@csstools/selector-specificity": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.0.tgz", + "integrity": "sha512-hBI9tfBtuPIi885ZsZ32IMEU/5nlZH/KOVYJCOh7gyMxaVLGmLedYqFN6Ui1LXkI8JlC8IsuC0rF0btcRZKd5g==", "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.5", - "to-fast-properties": "^2.0.0" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^6.0.13" } }, "node_modules/@nodelib/fs.scandir": { @@ -448,9 +231,9 @@ } }, "node_modules/@nodelib/fs.walk": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz", - "integrity": "sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -460,52 +243,16 @@ "node": ">= 8" } }, - "node_modules/@stylelint/postcss-css-in-js": { - "version": "0.37.2", - "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz", - "integrity": "sha512-nEhsFoJurt8oUmieT8qy4nk81WRHmJynmVwn/Vts08PL9fhgIsMhk1GId5yAN643OzqEEb5S/6At2TZW7pqPDA==", - "dev": true, - "dependencies": { - "@babel/core": ">=7.9.0" - }, - "peerDependencies": { - "postcss": ">=7.0.0", - "postcss-syntax": ">=0.36.2" - } - }, - "node_modules/@stylelint/postcss-markdown": { - "version": "0.36.2", - "resolved": "https://registry.npmjs.org/@stylelint/postcss-markdown/-/postcss-markdown-0.36.2.tgz", - "integrity": "sha512-2kGbqUVJUGE8dM+bMzXG/PYUWKkjLIkRLWNh39OaADkiabDRdw8ATFCgbMz5xdIcvwspPAluSL7uY+ZiTWdWmQ==", - "dev": true, - "dependencies": { - "remark": "^13.0.0", - "unist-util-find-all-after": "^3.0.2" - }, - "peerDependencies": { - "postcss": ">=7.0.0", - "postcss-syntax": ">=0.36.2" - } - }, - "node_modules/@types/mdast": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.3.tgz", - "integrity": "sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw==", - "dev": true, - "dependencies": { - "@types/unist": "*" - } - }, "node_modules/@types/minimist": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", - "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.3.tgz", + "integrity": "sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A==", "dev": true }, "node_modules/@types/normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.2.tgz", + "integrity": "sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==", "dev": true }, "node_modules/@types/parse-json": { @@ -514,12 +261,6 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, - "node_modules/@types/unist": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", - "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==", - "dev": true - }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -534,9 +275,9 @@ } }, "node_modules/ajv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.0.tgz", - "integrity": "sha512-cnUG4NSBiM4YFBxgZIj/In3/6KX+rQ2l2YPRVcvAMQGWEPKuXoPIhxzwqh31jA3IPbI4qEOp/5ILI4ynioXsGQ==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", @@ -589,9 +330,9 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "engines": { "node": ">=8" @@ -661,6 +402,12 @@ "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "node_modules/arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -808,7 +555,7 @@ "node_modules/arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -877,28 +624,6 @@ "node": ">= 4.5.0" } }, - "node_modules/autoprefixer": { - "version": "9.8.6", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", - "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", - "dev": true, - "dependencies": { - "browserslist": "^4.12.0", - "caniuse-lite": "^1.0.30001109", - "colorette": "^1.2.1", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "postcss": "^7.0.32", - "postcss-value-parser": "^4.1.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "funding": { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - } - }, "node_modules/bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", @@ -919,16 +644,6 @@ "node": ">= 0.10" } }, - "node_modules/bail": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", - "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/balanced-match": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", @@ -1021,29 +736,6 @@ "node": ">=0.10.0" } }, - "node_modules/browserslist": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", - "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", - "dev": true, - "dependencies": { - "caniuse-lite": "^1.0.30001219", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.723", - "escalade": "^3.1.1", - "node-releases": "^1.1.71" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, "node_modules/buffer-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", @@ -1102,39 +794,45 @@ } }, "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-7.0.2.tgz", + "integrity": "sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==", "dev": true, "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" + "camelcase": "^6.3.0", + "map-obj": "^4.1.0", + "quick-lru": "^5.1.1", + "type-fest": "^1.2.1" }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001239", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001239.tgz", - "integrity": "sha512-cyBkXJDMeI4wthy8xJ2FvDU6+0dtcZSJW3voUF8+e9f1bBeuvyZfc3PNbkOETyhbR+dGCPzn9E7MA3iwzusOhQ==", + "node_modules/camelcase-keys/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true, + "engines": { + "node": ">=10" + }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/chalk": { @@ -1153,36 +851,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/character-entities": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", - "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", - "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-reference-invalid": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", - "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/child-process-promise": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/child-process-promise/-/child-process-promise-2.2.1.tgz", @@ -1468,27 +1136,6 @@ "node": ">= 0.10" } }, - "node_modules/clone-regexp": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-2.2.0.tgz", - "integrity": "sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==", - "dev": true, - "dependencies": { - "is-regexp": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/clone-regexp/node_modules/is-regexp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-2.1.0.tgz", - "integrity": "sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/clone-stats": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", @@ -1569,6 +1216,12 @@ "color-support": "bin.js" } }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true + }, "node_modules/colorette": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", @@ -1671,6 +1324,28 @@ "which": "^1.2.9" } }, + "node_modules/css-functions-list": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.0.tgz", + "integrity": "sha512-d/jBMPyYybkkLVypgtGv12R+pIFw4/f/IHtCTxWpZc8ofTYOPigIgmA6vu5rMHartZC+WuXhBUHfnyNUIQSYrg==", + "dev": true, + "engines": { + "node": ">=12.22" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -1694,9 +1369,9 @@ } }, "node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -1720,9 +1395,9 @@ } }, "node_modules/decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", "dev": true, "dependencies": { "decamelize": "^1.1.0", @@ -1730,12 +1405,15 @@ }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/decamelize-keys/node_modules/map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -1823,62 +1501,6 @@ "node": ">=8" } }, - "node_modules/dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "dev": true, - "dependencies": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - } - }, - "node_modules/dom-serializer/node_modules/domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true - }, - "node_modules/domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "dev": true, - "dependencies": { - "domelementtype": "1" - } - }, - "node_modules/domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "dev": true, - "dependencies": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, "node_modules/duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -1913,12 +1535,6 @@ "node": ">=0.10.0" } }, - "node_modules/electron-to-chromium": { - "version": "1.3.758", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.758.tgz", - "integrity": "sha512-StYtiDbgZdjcck3OLwsVVVif7QDuD5m5v2gF+XpETp5lHa7X0y3129YBlYaHRPyj1fep1oAaC6i//gAdp+rhbw==", - "dev": true - }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1955,12 +1571,6 @@ "node": ">=6" } }, - "node_modules/entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -2014,15 +1624,6 @@ "es6-symbol": "^3.1.1" } }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -2084,18 +1685,6 @@ "node": ">= 8" } }, - "node_modules/execall": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/execall/-/execall-2.0.0.tgz", - "integrity": "sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==", - "dev": true, - "dependencies": { - "clone-regexp": "^2.1.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -2307,20 +1896,19 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", - "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", + "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" + "micromatch": "^4.0.4" }, "engines": { - "node": ">=8" + "node": ">=8.6.0" } }, "node_modules/fast-glob/node_modules/braces": { @@ -2369,13 +1957,13 @@ } }, "node_modules/fast-glob/node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" }, "engines": { "node": ">=8.6" @@ -2400,15 +1988,18 @@ "dev": true }, "node_modules/fastest-levenshtein": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", - "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", - "dev": true + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } }, "node_modules/fastq": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", - "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -2449,16 +2040,19 @@ } }, "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "dependencies": { - "locate-path": "^5.0.0", + "locate-path": "^6.0.0", "path-exists": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/findup-sync": { @@ -2629,15 +2223,6 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", @@ -2664,18 +2249,6 @@ "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", "dev": true }, - "node_modules/get-stdin": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", - "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -2808,26 +2381,17 @@ "node": ">=0.10.0" } }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" }, "engines": { @@ -2855,21 +2419,6 @@ "node": ">= 0.10" } }, - "node_modules/gonzales-pe": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz", - "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "gonzales": "bin/gonzales.js" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", @@ -3077,9 +2626,9 @@ } }, "node_modules/hosted-git-info": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", - "integrity": "sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -3107,40 +2656,15 @@ "dev": true }, "node_modules/html-tags": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", - "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", "dev": true, "engines": { "node": ">=8" - } - }, - "node_modules/htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "dev": true, - "dependencies": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" - } - }, - "node_modules/htmlparser2/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" }, - "engines": { - "node": ">= 6" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/human-signals": { @@ -3165,9 +2689,9 @@ } }, "node_modules/ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true, "engines": { "node": ">= 4" @@ -3201,7 +2725,7 @@ "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "engines": { "node": ">=0.8.19" @@ -3290,30 +2814,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", - "dev": true, - "dependencies": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -3332,33 +2832,10 @@ "node": ">=0.10.0" } }, - "node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "engines": { - "node": ">=4" - } - }, "node_modules/is-core-module": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", - "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", "dev": true, "dependencies": { "has": "^1.0.3" @@ -3388,16 +2865,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", @@ -3455,19 +2922,9 @@ "dev": true, "dependencies": { "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-hexadecimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", - "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + }, + "engines": { + "node": ">=0.10.0" } }, "node_modules/is-negated-glob": { @@ -3521,7 +2978,7 @@ "node_modules/is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -3566,12 +3023,6 @@ "node": ">=8" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, "node_modules/is-unc-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", @@ -3647,16 +3098,16 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "bin": { - "jsesc": "bin/jsesc" + "dependencies": { + "argparse": "^2.0.1" }, - "engines": { - "node": ">=4" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, "node_modules/json-parse-even-better-errors": { @@ -3677,18 +3128,6 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/just-debounce": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", @@ -3705,9 +3144,9 @@ } }, "node_modules/known-css-properties": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.21.0.tgz", - "integrity": "sha512-sZLUnTqimCkvkgRS+kbPlYW5o8q5w1cu+uIisKpEWkj31I8mx8kNG162DwRav8Zirkva6N5uoFsm9kzK4mUXjw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.28.0.tgz", + "integrity": "sha512-9pSL5XB4J+ifHP0e0jmmC98OGC1nL8/JjS+fi6mnTlIf//yt/MfVLtKg7S6nCtj/8KTcWX7nRlY0XywoYY1ISQ==", "dev": true }, "node_modules/last-run": { @@ -3933,15 +3372,18 @@ } }, "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "dependencies": { - "p-locate": "^4.1.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lodash": { @@ -3950,16 +3392,10 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", "dev": true }, "node_modules/log-symbols": { @@ -4027,16 +3463,6 @@ "node": ">=8" } }, - "node_modules/longest-streak": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", - "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", @@ -4078,9 +3504,9 @@ } }, "node_modules/map-obj": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz", - "integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", "dev": true, "engines": { "node": ">=8" @@ -4153,70 +3579,43 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/mdast-util-from-markdown": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", - "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-to-string": "^2.0.0", - "micromark": "~2.11.0", - "parse-entities": "^2.0.0", - "unist-util-stringify-position": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-markdown": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz", - "integrity": "sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "longest-streak": "^2.0.0", - "mdast-util-to-string": "^2.0.0", - "parse-entities": "^2.0.0", - "repeat-string": "^1.0.0", - "zwitch": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", - "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true }, "node_modules/meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/meow/-/meow-10.1.5.tgz", + "integrity": "sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==", "dev": true, "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", + "@types/minimist": "^1.2.2", + "camelcase-keys": "^7.0.0", + "decamelize": "^5.0.0", "decamelize-keys": "^1.1.0", "hard-rejection": "^2.1.0", "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" + "normalize-package-data": "^3.0.2", + "read-pkg-up": "^8.0.0", + "redent": "^4.0.0", + "trim-newlines": "^4.0.2", + "type-fest": "^1.2.2", + "yargs-parser": "^20.2.9" }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/decamelize": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", + "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==", + "dev": true, "engines": { "node": ">=10" }, @@ -4225,9 +3624,9 @@ } }, "node_modules/meow/node_modules/type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true, "engines": { "node": ">=10" @@ -4251,26 +3650,6 @@ "node": ">= 8" } }, - "node_modules/micromark": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", - "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "debug": "^4.0.0", - "parse-entities": "^2.0.0" - } - }, "node_modules/micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -4371,15 +3750,6 @@ "node": "*" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/minimist-options": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", @@ -4462,6 +3832,24 @@ "dev": true, "optional": true }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -4536,12 +3924,6 @@ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, - "node_modules/node-releases": { - "version": "1.1.73", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", - "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==", - "dev": true - }, "node_modules/node-version": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/node-version/-/node-version-1.2.0.tgz", @@ -4552,13 +3934,13 @@ } }, "node_modules/normalize-package-data": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.2.tgz", - "integrity": "sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", "dev": true, "dependencies": { "hosted-git-info": "^4.0.1", - "resolve": "^1.20.0", + "is-core-module": "^2.5.0", "semver": "^7.3.4", "validate-npm-package-license": "^3.0.1" }, @@ -4566,39 +3948,6 @@ "node": ">=10" } }, - "node_modules/normalize-package-data/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-package-data/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4608,21 +3957,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-selector": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/normalize-selector/-/normalize-selector-0.2.0.tgz", - "integrity": "sha1-0LFF62kRicY6eNIB3E/bEpPvDAM=", - "dev": true - }, "node_modules/now-and-later": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", @@ -4647,12 +3981,6 @@ "node": ">=8" } }, - "node_modules/num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", - "dev": true - }, "node_modules/number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -4891,30 +4219,33 @@ } }, "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "dependencies": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "dependencies": { - "p-limit": "^2.2.0" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-map": { @@ -4932,15 +4263,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -4953,24 +4275,6 @@ "node": ">=6" } }, - "node_modules/parse-entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", - "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", - "dev": true, - "dependencies": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/parse-filepath": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", @@ -5099,10 +4403,16 @@ "node": ">=8" } }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "engines": { "node": ">=8.6" @@ -5212,46 +4522,31 @@ } }, "node_modules/postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/postcss-html": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.36.0.tgz", - "integrity": "sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==", - "dev": true, - "dependencies": { - "htmlparser2": "^3.10.0" - }, - "peerDependencies": { - "postcss": ">=5.0.0", - "postcss-syntax": ">=0.36.0" - } - }, - "node_modules/postcss-less": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-3.1.4.tgz", - "integrity": "sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "dependencies": { - "postcss": "^7.0.14" + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" }, "engines": { - "node": ">=6.14.4" + "node": "^10 || ^12 || >=14" } }, "node_modules/postcss-media-query-parser": { @@ -5267,154 +4562,75 @@ "dev": true }, "node_modules/postcss-safe-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz", - "integrity": "sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g==", - "dev": true, - "dependencies": { - "postcss": "^7.0.26" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-sass": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.4.4.tgz", - "integrity": "sha512-BYxnVYx4mQooOhr+zer0qWbSPYnarAy8ZT7hAQtbxtgVf8gy+LSLT/hHGe35h14/pZDTw1DsxdbrwxBN++H+fg==", - "dev": true, - "dependencies": { - "gonzales-pe": "^4.3.0", - "postcss": "^7.0.21" - } - }, - "node_modules/postcss-scss": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-2.1.1.tgz", - "integrity": "sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", + "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", "dev": true, - "dependencies": { - "postcss": "^7.0.6" - }, "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", - "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", - "dev": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" + "node": ">=12.0" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-sorting": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-5.0.1.tgz", - "integrity": "sha512-Y9fUFkIhfrm6i0Ta3n+89j56EFqaNRdUKqXyRp6kvTcSXnmgEjaVowCXH+JBe9+YKWqd4nc28r2sgwnzJalccA==", - "dev": true, - "dependencies": { - "lodash": "^4.17.14", - "postcss": "^7.0.17" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" }, - "engines": { - "node": ">=8.7.0" - } - }, - "node_modules/postcss-syntax": { - "version": "0.36.2", - "resolved": "https://registry.npmjs.org/postcss-syntax/-/postcss-syntax-0.36.2.tgz", - "integrity": "sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==", - "dev": true, "peerDependencies": { - "postcss": ">=5.0.0" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", - "dev": true - }, - "node_modules/postcss/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" + "postcss": "^8.3.3" } }, - "node_modules/postcss/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/postcss-scss": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", + "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "engines": { - "node": ">=4" + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.29" } }, - "node_modules/postcss/node_modules/chalk/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", "dev": true, "dependencies": { - "has-flag": "^3.0.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { "node": ">=4" } }, - "node_modules/postcss/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/postcss-sorting": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-8.0.2.tgz", + "integrity": "sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q==", "dev": true, - "dependencies": { - "color-name": "1.1.3" + "peerDependencies": { + "postcss": "^8.4.20" } }, - "node_modules/postcss/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, - "node_modules/postcss/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -5464,9 +4680,9 @@ } }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "dev": true, "engines": { "node": ">=6" @@ -5493,89 +4709,74 @@ ] }, "node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "dev": true, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-6.0.0.tgz", + "integrity": "sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==", "dev": true, "dependencies": { "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" + "normalize-package-data": "^3.0.2", + "parse-json": "^5.2.0", + "type-fest": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-8.0.0.tgz", + "integrity": "sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==", "dev": true, "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" + "find-up": "^5.0.0", + "read-pkg": "^6.0.0", + "type-fest": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true, "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/read-pkg/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/readable-stream": { @@ -5620,16 +4821,31 @@ } }, "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-4.0.0.tgz", + "integrity": "sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==", "dev": true, "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" + "indent-string": "^5.0.0", + "strip-indent": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/redent/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/regex-not": { @@ -5682,47 +4898,6 @@ "node": ">=0.10.0" } }, - "node_modules/remark": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/remark/-/remark-13.0.0.tgz", - "integrity": "sha512-HDz1+IKGtOyWN+QgBiAT0kn+2s6ovOxHyPAFGKVE81VSzJ+mq7RwHFledEvB5F1p4iJvOah/LOKdFuzvRnNLCA==", - "dev": true, - "dependencies": { - "remark-parse": "^9.0.0", - "remark-stringify": "^9.0.0", - "unified": "^9.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-parse": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz", - "integrity": "sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==", - "dev": true, - "dependencies": { - "mdast-util-from-markdown": "^0.8.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-stringify": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-9.0.1.tgz", - "integrity": "sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg==", - "dev": true, - "dependencies": { - "mdast-util-to-markdown": "^0.6.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/remove-bom-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", @@ -6142,12 +5317,18 @@ } }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/semver-compare": { @@ -6168,6 +5349,24 @@ "node": ">= 0.10" } }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -6439,6 +5638,15 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-resolve": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", @@ -6499,15 +5707,6 @@ "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==", "dev": true }, - "node_modules/specificity": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.4.1.tgz", - "integrity": "sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==", - "dev": true, - "bin": { - "specificity": "bin/specificity" - } - }, "node_modules/split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -6690,14 +5889,14 @@ } }, "node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" @@ -6718,12 +5917,12 @@ } }, "node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" @@ -6751,15 +5950,18 @@ } }, "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz", + "integrity": "sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==", "dev": true, "dependencies": { - "min-indent": "^1.0.0" + "min-indent": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/style-search": { @@ -6769,65 +5971,57 @@ "dev": true }, "node_modules/stylelint": { - "version": "13.13.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.13.1.tgz", - "integrity": "sha512-Mv+BQr5XTUrKqAXmpqm6Ddli6Ief+AiPZkRsIrAoUKFuq/ElkUh9ZMYxXD0iQNZ5ADghZKLOWz1h7hTClB7zgQ==", + "version": "15.10.3", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.10.3.tgz", + "integrity": "sha512-aBQMMxYvFzJJwkmg+BUUg3YfPyeuCuKo2f+LOw7yYbU8AZMblibwzp9OV4srHVeQldxvSFdz0/Xu8blq2AesiA==", "dev": true, "dependencies": { - "@stylelint/postcss-css-in-js": "^0.37.2", - "@stylelint/postcss-markdown": "^0.36.2", - "autoprefixer": "^9.8.6", + "@csstools/css-parser-algorithms": "^2.3.1", + "@csstools/css-tokenizer": "^2.2.0", + "@csstools/media-query-list-parser": "^2.1.4", + "@csstools/selector-specificity": "^3.0.0", "balanced-match": "^2.0.0", - "chalk": "^4.1.1", - "cosmiconfig": "^7.0.0", - "debug": "^4.3.1", - "execall": "^2.0.0", - "fast-glob": "^3.2.5", - "fastest-levenshtein": "^1.0.12", + "colord": "^2.9.3", + "cosmiconfig": "^8.2.0", + "css-functions-list": "^3.2.0", + "css-tree": "^2.3.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.1", + "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^6.0.1", - "get-stdin": "^8.0.0", "global-modules": "^2.0.0", - "globby": "^11.0.3", + "globby": "^11.1.0", "globjoin": "^0.1.4", - "html-tags": "^3.1.0", - "ignore": "^5.1.8", + "html-tags": "^3.3.1", + "ignore": "^5.2.4", "import-lazy": "^4.0.0", "imurmurhash": "^0.1.4", - "known-css-properties": "^0.21.0", - "lodash": "^4.17.21", - "log-symbols": "^4.1.0", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.28.0", "mathml-tag-names": "^2.1.3", - "meow": "^9.0.0", - "micromatch": "^4.0.4", - "normalize-selector": "^0.2.0", - "postcss": "^7.0.35", - "postcss-html": "^0.36.0", - "postcss-less": "^3.1.4", - "postcss-media-query-parser": "^0.2.3", + "meow": "^10.1.5", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.27", "postcss-resolve-nested-selector": "^0.1.1", - "postcss-safe-parser": "^4.0.2", - "postcss-sass": "^0.4.4", - "postcss-scss": "^2.1.1", - "postcss-selector-parser": "^6.0.5", - "postcss-syntax": "^0.36.2", - "postcss-value-parser": "^4.1.0", + "postcss-safe-parser": "^6.0.0", + "postcss-selector-parser": "^6.0.13", + "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", - "slash": "^3.0.0", - "specificity": "^0.4.1", - "string-width": "^4.2.2", - "strip-ansi": "^6.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", "style-search": "^0.1.0", - "sugarss": "^2.0.0", + "supports-hyperlinks": "^3.0.0", "svg-tags": "^1.0.0", - "table": "^6.6.0", - "v8-compile-cache": "^2.3.0", - "write-file-atomic": "^3.0.3" + "table": "^6.8.1", + "write-file-atomic": "^5.0.1" }, "bin": { - "stylelint": "bin/stylelint.js" + "stylelint": "bin/stylelint.mjs" }, "engines": { - "node": ">=10.13.0" + "node": "^14.13.1 || >=16.0.0" }, "funding": { "type": "opencollective", @@ -6835,52 +6029,64 @@ } }, "node_modules/stylelint-config-sass-guidelines": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-sass-guidelines/-/stylelint-config-sass-guidelines-8.0.0.tgz", - "integrity": "sha512-v21iDWtzpfhuKJlYKpoE1vjp+GT8Cr6ZBWwMx/jf+eCEblZgAIDVVjgAELoDLhVj17DcEFwlIKJBMfrdAmXg0Q==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-sass-guidelines/-/stylelint-config-sass-guidelines-10.0.0.tgz", + "integrity": "sha512-+Rr2Dd4b72CWA4qoj1Kk+y449nP/WJsrD0nzQAWkmPPIuyVcy2GMIcfNr0Z8JJOLjRvtlkKxa49FCNXMePBikQ==", "dev": true, "dependencies": { - "stylelint-order": "^4.0.0", - "stylelint-scss": "^3.18.0" + "postcss-scss": "^4.0.6", + "stylelint-scss": "^4.4.0" }, "engines": { - "node": ">=10.0.0" + "node": "^14.13.1 || >=16.13.0 || >=18.0.0" + }, + "peerDependencies": { + "postcss": "^8.4.21", + "stylelint": "^15.2.0" + } + }, + "node_modules/stylelint-config-sass-guidelines/node_modules/stylelint-scss": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.7.0.tgz", + "integrity": "sha512-TSUgIeS0H3jqDZnby1UO1Qv3poi1N8wUYIJY6D1tuUq2MN3lwp/rITVo0wD+1SWTmRm0tNmGO0b7nKInnqF6Hg==", + "dev": true, + "dependencies": { + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" }, "peerDependencies": { - "stylelint": "^13.7.0" + "stylelint": "^14.5.1 || ^15.0.0" } }, "node_modules/stylelint-order": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-4.1.0.tgz", - "integrity": "sha512-sVTikaDvMqg2aJjh4r48jsdfmqLT+nqB1MOsaBnvM3OwLx4S+WXcsxsgk5w18h/OZoxZCxuyXMh61iBHcj9Qiw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.3.tgz", + "integrity": "sha512-1j1lOb4EU/6w49qZeT2SQVJXm0Ht+Qnq9GMfUa3pMwoyojIWfuA+JUDmoR97Bht1RLn4ei0xtLGy87M7d29B1w==", "dev": true, "dependencies": { - "lodash": "^4.17.15", - "postcss": "^7.0.31", - "postcss-sorting": "^5.0.1" + "postcss": "^8.4.21", + "postcss-sorting": "^8.0.2" }, "peerDependencies": { - "stylelint": "^10.0.1 || ^11.0.0 || ^12.0.0 || ^13.0.0" + "stylelint": "^14.0.0 || ^15.0.0" } }, "node_modules/stylelint-scss": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-3.19.0.tgz", - "integrity": "sha512-Ic5bsmpS4wVucOw44doC1Yi9f5qbeVL4wPFiEOaUElgsOuLEN6Ofn/krKI8BeNL2gAn53Zu+IcVV4E345r6rBw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-5.2.1.tgz", + "integrity": "sha512-ZoTJUM85/qqpQHfEppjW/St//8s6p9Qsg8deWlYlr56F9iUgC9vXeIDQvH4odkRRJLTLFQzYMALSOFCQ3MDkgw==", "dev": true, "dependencies": { - "lodash": "^4.17.15", + "known-css-properties": "^0.28.0", "postcss-media-query-parser": "^0.2.3", "postcss-resolve-nested-selector": "^0.1.1", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": ">=8" + "postcss-selector-parser": "^6.0.13", + "postcss-value-parser": "^4.2.0" }, "peerDependencies": { - "stylelint": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0" + "stylelint": "^14.5.1 || ^15.0.0" } }, "node_modules/stylelint/node_modules/braces": { @@ -6895,6 +6101,32 @@ "node": ">=8" } }, + "node_modules/stylelint/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/stylelint/node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -6952,13 +6184,13 @@ } }, "node_modules/stylelint/node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" }, "engines": { "node": ">=8.6" @@ -6985,15 +6217,6 @@ "node": ">=8.0" } }, - "node_modules/sugarss": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-2.0.0.tgz", - "integrity": "sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ==", - "dev": true, - "dependencies": { - "postcss": "^7.0.2" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -7006,6 +6229,19 @@ "node": ">=8" } }, + "node_modules/supports-hyperlinks": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz", + "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + } + }, "node_modules/sver-compat": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", @@ -7023,17 +6259,16 @@ "dev": true }, "node_modules/table": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", - "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", "dev": true, "dependencies": { "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0" + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=10.0.0" @@ -7113,15 +6348,6 @@ "node": ">=0.10.0" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -7246,22 +6472,15 @@ "dev": true }, "node_modules/trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz", + "integrity": "sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==", "dev": true, "engines": { - "node": ">=8" - } - }, - "node_modules/trough": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", - "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", - "dev": true, + "node": ">=12" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/tslib": { @@ -7294,15 +6513,6 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, "node_modules/unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", @@ -7342,33 +6552,6 @@ "node": ">= 0.10" } }, - "node_modules/unified": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.1.tgz", - "integrity": "sha512-juWjuI8Z4xFg8pJbnEZ41b5xjGUWGHqXALmBZ3FC3WX0PIx1CZBIIJ6mXbYMcf6Yw4Fi0rFUTA1cdz/BglbOhA==", - "dev": true, - "dependencies": { - "bail": "^1.0.0", - "extend": "^3.0.0", - "is-buffer": "^2.0.0", - "is-plain-obj": "^2.0.0", - "trough": "^1.0.0", - "vfile": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unified/node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -7394,42 +6577,6 @@ "through2-filter": "^3.0.0" } }, - "node_modules/unist-util-find-all-after": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-3.0.2.tgz", - "integrity": "sha512-xaTC/AGZ0rIM2gM28YVRAFPIZpzbpDtU3dRmp7EXlNVA8ziQc4hY3H7BHXM1J49nEmiqc3svnqMReW+PGqbZKQ==", - "dev": true, - "dependencies": { - "unist-util-is": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-is": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", - "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", - "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -7519,12 +6666,6 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, "node_modules/v8flags": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", @@ -7556,36 +6697,6 @@ "node": ">= 0.10" } }, - "node_modules/vfile": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", - "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "is-buffer": "^2.0.0", - "unist-util-stringify-position": "^2.0.0", - "vfile-message": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", - "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-stringify-position": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/vinyl": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", @@ -7740,15 +6851,28 @@ "dev": true }, "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, "dependencies": { "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/xtend": { @@ -7970,14 +7094,16 @@ "object.assign": "^4.1.0" } }, - "node_modules/zwitch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", - "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==", + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "engines": { + "node": ">=10" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://github.com/sponsors/sindresorhus" } } }, @@ -7991,199 +7117,12 @@ "@babel/highlight": "^7.14.5" } }, - "@babel/compat-data": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz", - "integrity": "sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw==", - "dev": true - }, - "@babel/core": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.6.tgz", - "integrity": "sha512-gJnOEWSqTk96qG5BoIrl5bVtc23DCycmIePPYnamY9RboYdI4nFy5vAQMSl81O5K/W0sLDWfGysnOECC+KUUCA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", - "@babel/helper-compilation-targets": "^7.14.5", - "@babel/helper-module-transforms": "^7.14.5", - "@babel/helpers": "^7.14.6", - "@babel/parser": "^7.14.6", - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "@babel/helper-compilation-targets": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz", - "integrity": "sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.14.5", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", - "semver": "^6.3.0" - } - }, - "@babel/helper-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", - "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", - "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", - "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz", - "integrity": "sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-module-imports": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", - "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-module-transforms": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", - "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.14.5", - "@babel/helper-replace-supers": "^7.14.5", - "@babel/helper-simple-access": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", - "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-replace-supers": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", - "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.14.5", - "@babel/helper-optimise-call-expression": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-simple-access": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.5.tgz", - "integrity": "sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", - "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, "@babel/helper-validator-identifier": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", "dev": true }, - "@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", - "dev": true - }, - "@babel/helpers": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.6.tgz", - "integrity": "sha512-yesp1ENQBiLI+iYHSJdoZKUtRpfTlL1grDIX9NRlAVppljLw/4tTyYupIB7uIYmC3stW/imAv8EqaKaS/ibmeA==", - "dev": true, - "requires": { - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" - } - }, "@babel/highlight": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", @@ -8247,49 +7186,32 @@ } } }, - "@babel/parser": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz", - "integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==", - "dev": true - }, - "@babel/template": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", - "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "@csstools/css-parser-algorithms": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.2.tgz", + "integrity": "sha512-sLYGdAdEY2x7TSw9FtmdaTrh2wFtRJO5VMbBrA8tEqEod7GEggFmxTSK9XqExib3yMuYNcvcTdCZIP6ukdjAIA==", "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5" - } + "requires": {} + }, + "@csstools/css-tokenizer": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.2.1.tgz", + "integrity": "sha512-Zmsf2f/CaEPWEVgw29odOj+WEVoiJy9s9NOv5GgNY9mZ1CZ7394By6wONrONrTsnNDv6F9hR02nvFihrGVGHBg==", + "dev": true }, - "@babel/traverse": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz", - "integrity": "sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ==", + "@csstools/media-query-list-parser": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.5.tgz", + "integrity": "sha512-IxVBdYzR8pYe89JiyXQuYk4aVVoCPhMJkz6ElRwlVysjwURTsTk/bmY/z4FfeRE+CRBMlykPwXEVUg8lThv7AQ==", "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-hoist-variables": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.14.7", - "@babel/types": "^7.14.5", - "debug": "^4.1.0", - "globals": "^11.1.0" - } + "requires": {} }, - "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "@csstools/selector-specificity": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.0.tgz", + "integrity": "sha512-hBI9tfBtuPIi885ZsZ32IMEU/5nlZH/KOVYJCOh7gyMxaVLGmLedYqFN6Ui1LXkI8JlC8IsuC0rF0btcRZKd5g==", "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "to-fast-properties": "^2.0.0" - } + "requires": {} }, "@nodelib/fs.scandir": { "version": "2.1.5", @@ -8308,53 +7230,25 @@ "dev": true }, "@nodelib/fs.walk": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz", - "integrity": "sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "requires": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, - "@stylelint/postcss-css-in-js": { - "version": "0.37.2", - "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz", - "integrity": "sha512-nEhsFoJurt8oUmieT8qy4nk81WRHmJynmVwn/Vts08PL9fhgIsMhk1GId5yAN643OzqEEb5S/6At2TZW7pqPDA==", - "dev": true, - "requires": { - "@babel/core": ">=7.9.0" - } - }, - "@stylelint/postcss-markdown": { - "version": "0.36.2", - "resolved": "https://registry.npmjs.org/@stylelint/postcss-markdown/-/postcss-markdown-0.36.2.tgz", - "integrity": "sha512-2kGbqUVJUGE8dM+bMzXG/PYUWKkjLIkRLWNh39OaADkiabDRdw8ATFCgbMz5xdIcvwspPAluSL7uY+ZiTWdWmQ==", - "dev": true, - "requires": { - "remark": "^13.0.0", - "unist-util-find-all-after": "^3.0.2" - } - }, - "@types/mdast": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.3.tgz", - "integrity": "sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw==", - "dev": true, - "requires": { - "@types/unist": "*" - } - }, "@types/minimist": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", - "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.3.tgz", + "integrity": "sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A==", "dev": true }, "@types/normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.2.tgz", + "integrity": "sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==", "dev": true }, "@types/parse-json": { @@ -8363,12 +7257,6 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, - "@types/unist": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", - "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==", - "dev": true - }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -8380,9 +7268,9 @@ } }, "ajv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.0.tgz", - "integrity": "sha512-cnUG4NSBiM4YFBxgZIj/In3/6KX+rQ2l2YPRVcvAMQGWEPKuXoPIhxzwqh31jA3IPbI4qEOp/5ILI4ynioXsGQ==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -8419,9 +7307,9 @@ } }, "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { @@ -8475,6 +7363,12 @@ "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -8584,7 +7478,7 @@ "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", "dev": true }, "assign-symbols": { @@ -8632,21 +7526,6 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, - "autoprefixer": { - "version": "9.8.6", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", - "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", - "dev": true, - "requires": { - "browserslist": "^4.12.0", - "caniuse-lite": "^1.0.30001109", - "colorette": "^1.2.1", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "postcss": "^7.0.32", - "postcss-value-parser": "^4.1.0" - } - }, "bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", @@ -8664,12 +7543,6 @@ "now-and-later": "^2.0.0" } }, - "bail": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", - "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", - "dev": true - }, "balanced-match": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", @@ -8754,19 +7627,6 @@ "to-regex": "^3.0.1" } }, - "browserslist": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", - "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001219", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.723", - "escalade": "^3.1.1", - "node-releases": "^1.1.71" - } - }, "buffer-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", @@ -8813,28 +7673,31 @@ "dev": true }, "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true }, "camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-7.0.2.tgz", + "integrity": "sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==", "dev": true, "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" + "camelcase": "^6.3.0", + "map-obj": "^4.1.0", + "quick-lru": "^5.1.1", + "type-fest": "^1.2.1" + }, + "dependencies": { + "type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true + } } }, - "caniuse-lite": { - "version": "1.0.30001239", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001239.tgz", - "integrity": "sha512-cyBkXJDMeI4wthy8xJ2FvDU6+0dtcZSJW3voUF8+e9f1bBeuvyZfc3PNbkOETyhbR+dGCPzn9E7MA3iwzusOhQ==", - "dev": true - }, "chalk": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", @@ -8845,24 +7708,6 @@ "supports-color": "^7.1.0" } }, - "character-entities": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", - "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", - "dev": true - }, - "character-entities-legacy": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", - "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", - "dev": true - }, - "character-reference-invalid": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", - "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", - "dev": true - }, "child-process-promise": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/child-process-promise/-/child-process-promise-2.2.1.tgz", @@ -9090,27 +7935,10 @@ "dev": true }, "clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", - "dev": true - }, - "clone-regexp": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-2.2.0.tgz", - "integrity": "sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==", - "dev": true, - "requires": { - "is-regexp": "^2.0.0" - }, - "dependencies": { - "is-regexp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-2.1.0.tgz", - "integrity": "sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==", - "dev": true - } - } + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true }, "clone-stats": { "version": "1.0.0", @@ -9177,6 +8005,12 @@ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "dev": true }, + "colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true + }, "colorette": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", @@ -9267,6 +8101,22 @@ "which": "^1.2.9" } }, + "css-functions-list": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.0.tgz", + "integrity": "sha512-d/jBMPyYybkkLVypgtGv12R+pIFw4/f/IHtCTxWpZc8ofTYOPigIgmA6vu5rMHartZC+WuXhBUHfnyNUIQSYrg==", + "dev": true + }, + "css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "requires": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + } + }, "cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -9284,9 +8134,9 @@ } }, "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -9299,9 +8149,9 @@ "dev": true }, "decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", "dev": true, "requires": { "decamelize": "^1.1.0", @@ -9311,7 +8161,7 @@ "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", "dev": true } } @@ -9377,55 +8227,6 @@ "path-type": "^4.0.0" } }, - "dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - }, - "dependencies": { - "domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", - "dev": true - }, - "entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true - } - } - }, - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true - }, - "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "dev": true, - "requires": { - "domelementtype": "1" - } - }, - "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "dev": true, - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -9459,12 +8260,6 @@ } } }, - "electron-to-chromium": { - "version": "1.3.758", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.758.tgz", - "integrity": "sha512-StYtiDbgZdjcck3OLwsVVVif7QDuD5m5v2gF+XpETp5lHa7X0y3129YBlYaHRPyj1fep1oAaC6i//gAdp+rhbw==", - "dev": true - }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -9497,12 +8292,6 @@ } } }, - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true - }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -9556,12 +8345,6 @@ "es6-symbol": "^3.1.1" } }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -9607,15 +8390,6 @@ } } }, - "execall": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/execall/-/execall-2.0.0.tgz", - "integrity": "sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==", - "dev": true, - "requires": { - "clone-regexp": "^2.1.0" - } - }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -9801,17 +8575,16 @@ "dev": true }, "fast-glob": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", - "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", + "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" + "micromatch": "^4.0.4" }, "dependencies": { "braces": { @@ -9848,13 +8621,13 @@ "dev": true }, "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" } }, "to-regex-range": { @@ -9875,15 +8648,15 @@ "dev": true }, "fastest-levenshtein": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", - "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", "dev": true }, "fastq": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", - "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -9918,12 +8691,12 @@ } }, "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "requires": { - "locate-path": "^5.0.0", + "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, @@ -10064,12 +8837,6 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", @@ -10093,12 +8860,6 @@ "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", "dev": true }, - "get-stdin": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", - "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", - "dev": true - }, "get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -10203,23 +8964,17 @@ "which": "^1.2.14" } }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, "globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" } }, @@ -10238,15 +8993,6 @@ "sparkles": "^1.0.0" } }, - "gonzales-pe": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz", - "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, "graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", @@ -10408,9 +9154,9 @@ } }, "hosted-git-info": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", - "integrity": "sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -10434,38 +9180,11 @@ } }, "html-tags": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", - "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", "dev": true }, - "htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "dev": true, - "requires": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -10479,9 +9198,9 @@ "dev": true }, "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true }, "import-fresh": { @@ -10503,7 +9222,7 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true }, "indent-string": { @@ -10573,22 +9292,6 @@ } } }, - "is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", - "dev": true - }, - "is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", - "dev": true, - "requires": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" - } - }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -10604,16 +9307,10 @@ "binary-extensions": "^1.0.0" } }, - "is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true - }, "is-core-module": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", - "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", "dev": true, "requires": { "has": "^1.0.3" @@ -10636,12 +9333,6 @@ } } }, - "is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", - "dev": true - }, "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", @@ -10688,12 +9379,6 @@ "is-extglob": "^2.1.1" } }, - "is-hexadecimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", - "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", - "dev": true - }, "is-negated-glob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", @@ -10735,7 +9420,7 @@ "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", "dev": true }, "is-plain-object": { @@ -10765,12 +9450,6 @@ "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "dev": true }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, "is-unc-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", @@ -10828,11 +9507,14 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } }, "json-parse-even-better-errors": { "version": "2.3.1", @@ -10852,12 +9534,6 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, "just-debounce": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", @@ -10871,9 +9547,9 @@ "dev": true }, "known-css-properties": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.21.0.tgz", - "integrity": "sha512-sZLUnTqimCkvkgRS+kbPlYW5o8q5w1cu+uIisKpEWkj31I8mx8kNG162DwRav8Zirkva6N5uoFsm9kzK4mUXjw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.28.0.tgz", + "integrity": "sha512-9pSL5XB4J+ifHP0e0jmmC98OGC1nL8/JjS+fi6mnTlIf//yt/MfVLtKg7S6nCtj/8KTcWX7nRlY0XywoYY1ISQ==", "dev": true }, "last-run": { @@ -11054,12 +9730,12 @@ } }, "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "requires": { - "p-locate": "^4.1.0" + "p-locate": "^5.0.0" } }, "lodash": { @@ -11068,16 +9744,10 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, "lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", "dev": true }, "log-symbols": { @@ -11126,12 +9796,6 @@ } } }, - "longest-streak": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", - "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", - "dev": true - }, "lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", @@ -11166,9 +9830,9 @@ "dev": true }, "map-obj": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz", - "integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", "dev": true }, "map-visit": { @@ -11221,63 +9885,42 @@ "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", "dev": true }, - "mdast-util-from-markdown": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", - "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==", - "dev": true, - "requires": { - "@types/mdast": "^3.0.0", - "mdast-util-to-string": "^2.0.0", - "micromark": "~2.11.0", - "parse-entities": "^2.0.0", - "unist-util-stringify-position": "^2.0.0" - } - }, - "mdast-util-to-markdown": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz", - "integrity": "sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==", - "dev": true, - "requires": { - "@types/unist": "^2.0.0", - "longest-streak": "^2.0.0", - "mdast-util-to-string": "^2.0.0", - "parse-entities": "^2.0.0", - "repeat-string": "^1.0.0", - "zwitch": "^1.0.0" - } - }, - "mdast-util-to-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", - "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "dev": true }, "meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/meow/-/meow-10.1.5.tgz", + "integrity": "sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==", "dev": true, "requires": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", + "@types/minimist": "^1.2.2", + "camelcase-keys": "^7.0.0", + "decamelize": "^5.0.0", "decamelize-keys": "^1.1.0", "hard-rejection": "^2.1.0", "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" + "normalize-package-data": "^3.0.2", + "read-pkg-up": "^8.0.0", + "redent": "^4.0.0", + "trim-newlines": "^4.0.2", + "type-fest": "^1.2.2", + "yargs-parser": "^20.2.9" }, "dependencies": { + "decamelize": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", + "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==", + "dev": true + }, "type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true } } @@ -11294,16 +9937,6 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, - "micromark": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", - "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", - "dev": true, - "requires": { - "debug": "^4.0.0", - "parse-entities": "^2.0.0" - } - }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -11382,12 +10015,6 @@ "brace-expansion": "^1.1.7" } }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true - }, "minimist-options": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", @@ -11456,6 +10083,12 @@ "dev": true, "optional": true }, + "nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "dev": true + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -11517,12 +10150,6 @@ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, - "node-releases": { - "version": "1.1.73", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", - "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==", - "dev": true - }, "node-version": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/node-version/-/node-version-1.2.0.tgz", @@ -11530,41 +10157,15 @@ "dev": true }, "normalize-package-data": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.2.tgz", - "integrity": "sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", "dev": true, "requires": { "hosted-git-info": "^4.0.1", - "resolve": "^1.20.0", + "is-core-module": "^2.5.0", "semver": "^7.3.4", "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } } }, "normalize-path": { @@ -11573,18 +10174,6 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "dev": true - }, - "normalize-selector": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/normalize-selector/-/normalize-selector-0.2.0.tgz", - "integrity": "sha1-0LFF62kRicY6eNIB3E/bEpPvDAM=", - "dev": true - }, "now-and-later": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", @@ -11603,12 +10192,6 @@ "path-key": "^3.0.0" } }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", - "dev": true - }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -11794,21 +10377,21 @@ } }, "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" } }, "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "requires": { - "p-limit": "^2.2.0" + "p-limit": "^3.0.2" } }, "p-map": { @@ -11820,12 +10403,6 @@ "aggregate-error": "^3.0.0" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -11835,20 +10412,6 @@ "callsites": "^3.0.0" } }, - "parse-entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", - "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", - "dev": true, - "requires": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" - } - }, "parse-filepath": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", @@ -11941,10 +10504,16 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, "pify": { @@ -11984,137 +10553,56 @@ "dev": true, "requires": { "ansi-colors": "^1.0.1", - "arr-diff": "^4.0.0", - "arr-union": "^3.1.0", - "extend-shallow": "^3.0.2" - }, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - } - } - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" } }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "color-name": "1.1.3" + "is-plain-object": "^2.0.4" } }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "isobject": "^3.0.1" } } } }, - "postcss-html": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.36.0.tgz", - "integrity": "sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==", - "dev": true, - "requires": { - "htmlparser2": "^3.10.0" - } + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true }, - "postcss-less": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-3.1.4.tgz", - "integrity": "sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==", + "postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "requires": { - "postcss": "^7.0.14" + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" } }, "postcss-media-query-parser": { @@ -12130,37 +10618,23 @@ "dev": true }, "postcss-safe-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz", - "integrity": "sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g==", - "dev": true, - "requires": { - "postcss": "^7.0.26" - } - }, - "postcss-sass": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.4.4.tgz", - "integrity": "sha512-BYxnVYx4mQooOhr+zer0qWbSPYnarAy8ZT7hAQtbxtgVf8gy+LSLT/hHGe35h14/pZDTw1DsxdbrwxBN++H+fg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", + "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", "dev": true, - "requires": { - "gonzales-pe": "^4.3.0", - "postcss": "^7.0.21" - } + "requires": {} }, "postcss-scss": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-2.1.1.tgz", - "integrity": "sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", + "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", "dev": true, - "requires": { - "postcss": "^7.0.6" - } + "requires": {} }, "postcss-selector-parser": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", - "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", "dev": true, "requires": { "cssesc": "^3.0.0", @@ -12168,26 +10642,16 @@ } }, "postcss-sorting": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-5.0.1.tgz", - "integrity": "sha512-Y9fUFkIhfrm6i0Ta3n+89j56EFqaNRdUKqXyRp6kvTcSXnmgEjaVowCXH+JBe9+YKWqd4nc28r2sgwnzJalccA==", - "dev": true, - "requires": { - "lodash": "^4.17.14", - "postcss": "^7.0.17" - } - }, - "postcss-syntax": { - "version": "0.36.2", - "resolved": "https://registry.npmjs.org/postcss-syntax/-/postcss-syntax-0.36.2.tgz", - "integrity": "sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-8.0.2.tgz", + "integrity": "sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q==", "dev": true, "requires": {} }, "postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, "pretty-hrtime": { @@ -12236,9 +10700,9 @@ } }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "dev": true }, "queue-microtask": { @@ -12248,70 +10712,46 @@ "dev": true }, "quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "dev": true }, "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-6.0.0.tgz", + "integrity": "sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==", "dev": true, "requires": { "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" + "normalize-package-data": "^3.0.2", + "parse-json": "^5.2.0", + "type-fest": "^1.0.1" }, "dependencies": { - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true } } }, "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-8.0.0.tgz", + "integrity": "sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==", "dev": true, "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" + "find-up": "^5.0.0", + "read-pkg": "^6.0.0", + "type-fest": "^1.0.1" }, "dependencies": { "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true } } @@ -12352,13 +10792,21 @@ } }, "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-4.0.0.tgz", + "integrity": "sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==", "dev": true, "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" + "indent-string": "^5.0.0", + "strip-indent": "^4.0.0" + }, + "dependencies": { + "indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true + } } }, "regex-not": { @@ -12401,35 +10849,6 @@ } } }, - "remark": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/remark/-/remark-13.0.0.tgz", - "integrity": "sha512-HDz1+IKGtOyWN+QgBiAT0kn+2s6ovOxHyPAFGKVE81VSzJ+mq7RwHFledEvB5F1p4iJvOah/LOKdFuzvRnNLCA==", - "dev": true, - "requires": { - "remark-parse": "^9.0.0", - "remark-stringify": "^9.0.0", - "unified": "^9.1.0" - } - }, - "remark-parse": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz", - "integrity": "sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==", - "dev": true, - "requires": { - "mdast-util-from-markdown": "^0.8.0" - } - }, - "remark-stringify": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-9.0.1.tgz", - "integrity": "sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg==", - "dev": true, - "requires": { - "mdast-util-to-markdown": "^0.6.0" - } - }, "remove-bom-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", @@ -12740,10 +11159,30 @@ } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } }, "semver-compare": { "version": "1.0.0", @@ -12986,6 +11425,12 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, "source-map-resolve": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", @@ -13043,12 +11488,6 @@ "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==", "dev": true }, - "specificity": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.4.1.tgz", - "integrity": "sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==", - "dev": true - }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -13200,14 +11639,14 @@ "dev": true }, "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "strip-ansi": "^6.0.1" } }, "stringify-object": { @@ -13222,12 +11661,12 @@ } }, "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" } }, "strip-bom": { @@ -13246,12 +11685,12 @@ "dev": true }, "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz", + "integrity": "sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==", "dev": true, "requires": { - "min-indent": "^1.0.0" + "min-indent": "^1.0.1" } }, "style-search": { @@ -13261,59 +11700,51 @@ "dev": true }, "stylelint": { - "version": "13.13.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.13.1.tgz", - "integrity": "sha512-Mv+BQr5XTUrKqAXmpqm6Ddli6Ief+AiPZkRsIrAoUKFuq/ElkUh9ZMYxXD0iQNZ5ADghZKLOWz1h7hTClB7zgQ==", + "version": "15.10.3", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.10.3.tgz", + "integrity": "sha512-aBQMMxYvFzJJwkmg+BUUg3YfPyeuCuKo2f+LOw7yYbU8AZMblibwzp9OV4srHVeQldxvSFdz0/Xu8blq2AesiA==", "dev": true, "requires": { - "@stylelint/postcss-css-in-js": "^0.37.2", - "@stylelint/postcss-markdown": "^0.36.2", - "autoprefixer": "^9.8.6", + "@csstools/css-parser-algorithms": "^2.3.1", + "@csstools/css-tokenizer": "^2.2.0", + "@csstools/media-query-list-parser": "^2.1.4", + "@csstools/selector-specificity": "^3.0.0", "balanced-match": "^2.0.0", - "chalk": "^4.1.1", - "cosmiconfig": "^7.0.0", - "debug": "^4.3.1", - "execall": "^2.0.0", - "fast-glob": "^3.2.5", - "fastest-levenshtein": "^1.0.12", + "colord": "^2.9.3", + "cosmiconfig": "^8.2.0", + "css-functions-list": "^3.2.0", + "css-tree": "^2.3.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.1", + "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^6.0.1", - "get-stdin": "^8.0.0", "global-modules": "^2.0.0", - "globby": "^11.0.3", + "globby": "^11.1.0", "globjoin": "^0.1.4", - "html-tags": "^3.1.0", - "ignore": "^5.1.8", + "html-tags": "^3.3.1", + "ignore": "^5.2.4", "import-lazy": "^4.0.0", "imurmurhash": "^0.1.4", - "known-css-properties": "^0.21.0", - "lodash": "^4.17.21", - "log-symbols": "^4.1.0", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.28.0", "mathml-tag-names": "^2.1.3", - "meow": "^9.0.0", - "micromatch": "^4.0.4", - "normalize-selector": "^0.2.0", - "postcss": "^7.0.35", - "postcss-html": "^0.36.0", - "postcss-less": "^3.1.4", - "postcss-media-query-parser": "^0.2.3", + "meow": "^10.1.5", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.27", "postcss-resolve-nested-selector": "^0.1.1", - "postcss-safe-parser": "^4.0.2", - "postcss-sass": "^0.4.4", - "postcss-scss": "^2.1.1", - "postcss-selector-parser": "^6.0.5", - "postcss-syntax": "^0.36.2", - "postcss-value-parser": "^4.1.0", + "postcss-safe-parser": "^6.0.0", + "postcss-selector-parser": "^6.0.13", + "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", - "slash": "^3.0.0", - "specificity": "^0.4.1", - "string-width": "^4.2.2", - "strip-ansi": "^6.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", "style-search": "^0.1.0", - "sugarss": "^2.0.0", + "supports-hyperlinks": "^3.0.0", "svg-tags": "^1.0.0", - "table": "^6.6.0", - "v8-compile-cache": "^2.3.0", - "write-file-atomic": "^3.0.3" + "table": "^6.8.1", + "write-file-atomic": "^5.0.1" }, "dependencies": { "braces": { @@ -13325,6 +11756,18 @@ "fill-range": "^7.0.1" } }, + "cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "requires": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -13367,13 +11810,13 @@ "dev": true }, "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" } }, "resolve-from": { @@ -13394,46 +11837,50 @@ } }, "stylelint-config-sass-guidelines": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-sass-guidelines/-/stylelint-config-sass-guidelines-8.0.0.tgz", - "integrity": "sha512-v21iDWtzpfhuKJlYKpoE1vjp+GT8Cr6ZBWwMx/jf+eCEblZgAIDVVjgAELoDLhVj17DcEFwlIKJBMfrdAmXg0Q==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-sass-guidelines/-/stylelint-config-sass-guidelines-10.0.0.tgz", + "integrity": "sha512-+Rr2Dd4b72CWA4qoj1Kk+y449nP/WJsrD0nzQAWkmPPIuyVcy2GMIcfNr0Z8JJOLjRvtlkKxa49FCNXMePBikQ==", "dev": true, "requires": { - "stylelint-order": "^4.0.0", - "stylelint-scss": "^3.18.0" + "postcss-scss": "^4.0.6", + "stylelint-scss": "^4.4.0" + }, + "dependencies": { + "stylelint-scss": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.7.0.tgz", + "integrity": "sha512-TSUgIeS0H3jqDZnby1UO1Qv3poi1N8wUYIJY6D1tuUq2MN3lwp/rITVo0wD+1SWTmRm0tNmGO0b7nKInnqF6Hg==", + "dev": true, + "requires": { + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" + } + } } }, "stylelint-order": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-4.1.0.tgz", - "integrity": "sha512-sVTikaDvMqg2aJjh4r48jsdfmqLT+nqB1MOsaBnvM3OwLx4S+WXcsxsgk5w18h/OZoxZCxuyXMh61iBHcj9Qiw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.3.tgz", + "integrity": "sha512-1j1lOb4EU/6w49qZeT2SQVJXm0Ht+Qnq9GMfUa3pMwoyojIWfuA+JUDmoR97Bht1RLn4ei0xtLGy87M7d29B1w==", "dev": true, "requires": { - "lodash": "^4.17.15", - "postcss": "^7.0.31", - "postcss-sorting": "^5.0.1" + "postcss": "^8.4.21", + "postcss-sorting": "^8.0.2" } }, "stylelint-scss": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-3.19.0.tgz", - "integrity": "sha512-Ic5bsmpS4wVucOw44doC1Yi9f5qbeVL4wPFiEOaUElgsOuLEN6Ofn/krKI8BeNL2gAn53Zu+IcVV4E345r6rBw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-5.2.1.tgz", + "integrity": "sha512-ZoTJUM85/qqpQHfEppjW/St//8s6p9Qsg8deWlYlr56F9iUgC9vXeIDQvH4odkRRJLTLFQzYMALSOFCQ3MDkgw==", "dev": true, "requires": { - "lodash": "^4.17.15", + "known-css-properties": "^0.28.0", "postcss-media-query-parser": "^0.2.3", "postcss-resolve-nested-selector": "^0.1.1", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - } - }, - "sugarss": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-2.0.0.tgz", - "integrity": "sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ==", - "dev": true, - "requires": { - "postcss": "^7.0.2" + "postcss-selector-parser": "^6.0.13", + "postcss-value-parser": "^4.2.0" } }, "supports-color": { @@ -13445,6 +11892,16 @@ "has-flag": "^4.0.0" } }, + "supports-hyperlinks": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz", + "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + } + }, "sver-compat": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", @@ -13462,17 +11919,16 @@ "dev": true }, "table": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", - "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", "dev": true, "requires": { "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0" + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" }, "dependencies": { "slice-ansi": { @@ -13541,12 +11997,6 @@ "is-negated-glob": "^1.0.0" } }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -13653,15 +12103,9 @@ "dev": true }, "trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", - "dev": true - }, - "trough": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", - "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz", + "integrity": "sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==", "dev": true }, "tslib": { @@ -13688,15 +12132,6 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, "unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", @@ -13727,28 +12162,6 @@ "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", "dev": true }, - "unified": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.1.tgz", - "integrity": "sha512-juWjuI8Z4xFg8pJbnEZ41b5xjGUWGHqXALmBZ3FC3WX0PIx1CZBIIJ6mXbYMcf6Yw4Fi0rFUTA1cdz/BglbOhA==", - "dev": true, - "requires": { - "bail": "^1.0.0", - "extend": "^3.0.0", - "is-buffer": "^2.0.0", - "is-plain-obj": "^2.0.0", - "trough": "^1.0.0", - "vfile": "^4.0.0" - }, - "dependencies": { - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - } - } - }, "union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -13771,30 +12184,6 @@ "through2-filter": "^3.0.0" } }, - "unist-util-find-all-after": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-3.0.2.tgz", - "integrity": "sha512-xaTC/AGZ0rIM2gM28YVRAFPIZpzbpDtU3dRmp7EXlNVA8ziQc4hY3H7BHXM1J49nEmiqc3svnqMReW+PGqbZKQ==", - "dev": true, - "requires": { - "unist-util-is": "^4.0.0" - } - }, - "unist-util-is": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", - "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", - "dev": true - }, - "unist-util-stringify-position": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", - "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", - "dev": true, - "requires": { - "@types/unist": "^2.0.2" - } - }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -13868,12 +12257,6 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, "v8flags": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", @@ -13899,28 +12282,6 @@ "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", "dev": true }, - "vfile": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", - "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", - "dev": true, - "requires": { - "@types/unist": "^2.0.0", - "is-buffer": "^2.0.0", - "unist-util-stringify-position": "^2.0.0", - "vfile-message": "^2.0.0" - } - }, - "vfile-message": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", - "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", - "dev": true, - "requires": { - "@types/unist": "^2.0.0", - "unist-util-stringify-position": "^2.0.0" - } - }, "vinyl": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", @@ -14056,15 +12417,21 @@ "dev": true }, "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, "requires": { "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" + "signal-exit": "^4.0.1" + }, + "dependencies": { + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + } } }, "xtend": { @@ -14246,10 +12613,10 @@ "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true }, - "zwitch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", - "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==", + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true } } diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/package.json b/www/civi-extensions/uk.co.vedaconsulting.gdpr/package.json index 203d2769a..6617c21d3 100644 --- a/www/civi-extensions/uk.co.vedaconsulting.gdpr/package.json +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/package.json @@ -15,9 +15,9 @@ "husky": "^6.0.0", "lint-staged": "^11.0.0", "sass": "^1.35.1", - "stylelint": "^13.13.1", - "stylelint-config-sass-guidelines": "^8.0.0", - "stylelint-order": "^4.1.0", - "stylelint-scss": "^3.19.0" + "stylelint": "^15.10.3", + "stylelint-config-sass-guidelines": "^10.0.0", + "stylelint-order": "^6.0.3", + "stylelint-scss": "^5.2.1" } } diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/CommunicationsPreferences.tpl b/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/CommunicationsPreferences.tpl index 42deabc9c..8a84d409a 100644 --- a/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/CommunicationsPreferences.tpl +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/CommunicationsPreferences.tpl @@ -3,7 +3,7 @@
- {if $communications_preferences_page_url } + {if $communications_preferences_page_url} {ts}Configure the display of the Communications Preferences page.{/ts} {/if}
@@ -12,7 +12,7 @@
{$form.$elementName.label}
{$form.$elementName.html} - {if $descriptions.$elementName} + {if array_key_exists($elementName, $descriptions)}
{$descriptions.$elementName}
{/if}
@@ -27,7 +27,7 @@ {* Channels block *}
-
{ $form.enable_channels.label }
+
{$form.enable_channels.label}
{$form.enable_channels.html}
@@ -151,7 +151,7 @@
{* end Completion block *} {* Confirmation Email Block *} -
+
{ts}Confirmation Email{/ts}
diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/ContributionPage/TermsAndConditions.tpl b/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/ContributionPage/TermsAndConditions.tpl index 95d064c19..6bcf36335 100644 --- a/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/ContributionPage/TermsAndConditions.tpl +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/ContributionPage/TermsAndConditions.tpl @@ -10,7 +10,7 @@
{$form.$terms_conditions_file_element_name.label}
{$form.$terms_conditions_file_element_name.html} - {if {$terms_conditions_current.url} + {if $terms_conditions_current.url}
{ts}Current: {$terms_conditions_current.label}{/ts} {/if} diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/ManageEvent/TermsAndConditions.tpl b/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/ManageEvent/TermsAndConditions.tpl index d1833c96f..c4143de65 100644 --- a/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/ManageEvent/TermsAndConditions.tpl +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/ManageEvent/TermsAndConditions.tpl @@ -10,7 +10,7 @@
{$form.$terms_conditions_file_element_name.label}
{$form.$terms_conditions_file_element_name.html} - {if {$terms_conditions_current.url} + {if $terms_conditions_current.url}
{ts}Current: {$terms_conditions_current.label}{/ts} {/if} diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/Settings.tpl b/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/Settings.tpl index 332368dd9..0bd3b0805 100644 --- a/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/Settings.tpl +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/Settings.tpl @@ -200,7 +200,7 @@
{if $sla_tc_version} Version: {$sla_tc_version}.
- {if $sla_tc_updated} + {if isset($sla_tc_updated) && $sla_tc_updated} Updated: {$sla_tc_updated} {/if}
@@ -268,11 +268,10 @@
{ts}Set defaults for Terms & Conditions. You can override these in the settings for individual Events and Contribution Pages.{/ts}
{* end .help *} - {foreach from="$entity_tc_elements" item="elem"} + {foreach from=$entity_tc_elements item="elem"}
{$form.$elem.label}
{$form.$elem.html} -
{* end .content *}
{* end .section *}
diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/Task/Contact.tpl b/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/Task/Contact.tpl index 73bd57223..fdcf80b05 100644 --- a/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/Task/Contact.tpl +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/Task/Contact.tpl @@ -13,9 +13,11 @@ {if ($help_text)}
{$help_text}
{/if} - {foreach from=$fields item=field} - {include file=$field.template field=$field} - {/foreach} + {if isset($fields)} + {foreach from=$fields item=field} + {include file=$field.template field=$field} + {/foreach} + {/if}
diff --git a/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/UpdatePreference.tpl b/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/UpdatePreference.tpl index 0d3171627..8195f641f 100644 --- a/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/UpdatePreference.tpl +++ b/www/civi-extensions/uk.co.vedaconsulting.gdpr/templates/CRM/Gdpr/Form/UpdatePreference.tpl @@ -86,7 +86,7 @@
- {if $isContactDueAcceptance} + {if isset($isContactDueAcceptance) && $isContactDueAcceptance}
{$tcIntro}
@@ -114,7 +114,7 @@
- {if $isCaptcha} + {if isset($isCaptcha) && $isCaptcha} {include file='CRM/common/ReCAPTCHA.tpl'} {/if} diff --git a/www/modules/civicrm/CRM/ACL/DAO/ACL.php b/www/modules/civicrm/CRM/ACL/DAO/ACL.php index 8655c8371..19960049f 100644 --- a/www/modules/civicrm/CRM/ACL/DAO/ACL.php +++ b/www/modules/civicrm/CRM/ACL/DAO/ACL.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/ACL/ACL.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:3d1a53d2aa2092299ce1a5b5ceb468f1) + * (GenCodeChecksum:53fb81cdf46cf58449634432851c0458) */ /** diff --git a/www/modules/civicrm/CRM/ACL/DAO/ACLCache.php b/www/modules/civicrm/CRM/ACL/DAO/ACLCache.php index 5687af27e..afe4b8085 100644 --- a/www/modules/civicrm/CRM/ACL/DAO/ACLCache.php +++ b/www/modules/civicrm/CRM/ACL/DAO/ACLCache.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/ACL/ACLCache.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:a4f7301d73ae6c7771075e585f120059) + * (GenCodeChecksum:171d575d22da5093f2acfdbb405817f7) */ /** diff --git a/www/modules/civicrm/CRM/ACL/DAO/ACLEntityRole.php b/www/modules/civicrm/CRM/ACL/DAO/ACLEntityRole.php index 795da1199..3568a715c 100644 --- a/www/modules/civicrm/CRM/ACL/DAO/ACLEntityRole.php +++ b/www/modules/civicrm/CRM/ACL/DAO/ACLEntityRole.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/ACL/ACLEntityRole.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:864dfaaffafb1aac5963e88c3b847c56) + * (GenCodeChecksum:9c0a932ecd4829b419ef162612857399) */ /** diff --git a/www/modules/civicrm/CRM/ACL/Form/WordPress/Permissions.php b/www/modules/civicrm/CRM/ACL/Form/WordPress/Permissions.php index 5839aaf0a..e3fa4697d 100644 --- a/www/modules/civicrm/CRM/ACL/Form/WordPress/Permissions.php +++ b/www/modules/civicrm/CRM/ACL/Form/WordPress/Permissions.php @@ -68,8 +68,8 @@ public function buildQuickForm() { $descArray = []; foreach ($permissionsDesc as $perm => $attr) { - if (count($attr) > 1) { - $descArray[$perm] = $attr['description'] ?? $attr[1]; + if (!empty($attr['description'])) { + $descArray[$perm] = $attr['description']; } } diff --git a/www/modules/civicrm/CRM/Activity/ActionMapping.php b/www/modules/civicrm/CRM/Activity/ActionMapping.php index 3f472bbc8..d150cad29 100644 --- a/www/modules/civicrm/CRM/Activity/ActionMapping.php +++ b/www/modules/civicrm/CRM/Activity/ActionMapping.php @@ -40,7 +40,7 @@ public function getEntityName(): string { return 'Activity'; } - public function modifySpec(\Civi\Api4\Service\Spec\RequestSpec $spec) { + public function modifyApiSpec(\Civi\Api4\Service\Spec\RequestSpec $spec) { $spec->getFieldByName('entity_value') ->setLabel(ts('Activity Type')); $spec->getFieldByName('entity_status') diff --git a/www/modules/civicrm/CRM/Activity/BAO/Activity.php b/www/modules/civicrm/CRM/Activity/BAO/Activity.php index 1b9722d5c..c64c7885a 100644 --- a/www/modules/civicrm/CRM/Activity/BAO/Activity.php +++ b/www/modules/civicrm/CRM/Activity/BAO/Activity.php @@ -1161,6 +1161,12 @@ public static function getAttachmentFileIds($activityID, $attachments) { * @param array $contactIds * @param int $sourceContactId This is the source contact Id * + * @deprecated since 5.71. + * + * This function has no core usage. There is some non-core usage. At some point + * we should add a suitable api & noisily deprecate this / set an tentative + * removal date. + * * @return array(bool $sent, int $activityId, int $success) * @throws CRM_Core_Exception */ @@ -1415,62 +1421,6 @@ public static function sendMessage( return TRUE; } - /** - * Combine all the importable fields from the lower levels object. - * - * The ordering is important, since currently we do not have a weight - * scheme. Adding weight is super important and should be done in the - * next week or so, before this can be called complete. - * - * @param bool $status - * @deprecated - * @return array - * array of importable Fields - */ - public static function &importableFields($status = FALSE) { - CRM_Core_Error::deprecatedFunctionWarning('api'); - if (empty(Civi::$statics[__CLASS__][__FUNCTION__])) { - Civi::$statics[__CLASS__][__FUNCTION__] = []; - if (!$status) { - $fields = ['' => ['title' => ts('- do not import -')]]; - } - else { - $fields = ['' => ['title' => ts('- Activity Fields -')]]; - } - - $tmpFields = CRM_Activity_DAO_Activity::import(); - $contactFields = CRM_Contact_BAO_Contact::importableFields('Individual', NULL); - - // Using new Dedupe rule. - $ruleParams = [ - 'contact_type' => 'Individual', - 'used' => 'Unsupervised', - ]; - $fieldsArray = CRM_Dedupe_BAO_DedupeRule::dedupeRuleFields($ruleParams); - - $tmpConatctField = []; - if (is_array($fieldsArray)) { - foreach ($fieldsArray as $value) { - $customFieldId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', - $value, - 'id', - 'column_name' - ); - $value = $customFieldId ? 'custom_' . $customFieldId : $value; - $tmpConatctField[trim($value)] = $contactFields[trim($value)]; - $tmpConatctField[trim($value)]['title'] = $tmpConatctField[trim($value)]['title'] . " (match to contact)"; - } - } - $tmpConatctField['external_identifier'] = $contactFields['external_identifier']; - $tmpConatctField['external_identifier']['title'] = $contactFields['external_identifier']['title'] . " (match to contact)"; - $fields = array_merge($fields, $tmpConatctField); - $fields = array_merge($fields, $tmpFields); - $fields = array_merge($fields, CRM_Core_BAO_CustomField::getFieldsForImport('Activity')); - Civi::$statics[__CLASS__][__FUNCTION__] = $fields; - } - return Civi::$statics[__CLASS__][__FUNCTION__]; - } - /** * @deprecated - use the api instead. * @@ -2387,11 +2337,11 @@ public static function getContactActivitySelector(&$params) { $activity['DT_RowAttr']['data-entity'] = 'activity'; $activity['DT_RowAttr']['data-id'] = $activityId; - $activity['activity_type'] = (!empty($activityIcons[$values['activity_type_id']]) ? ' ' : '') . $values['activity_type']; + $activity['activity_type'] = (!empty($activityIcons[$values['activity_type_id']]) ? ' ' : '') . htmlentities($values['activity_type']); $activity['subject'] = $values['subject']; if ($params['contact_id'] == $values['source_contact_id']) { - $activity['source_contact_name'] = $values['source_contact_name']; + $activity['source_contact_name'] = htmlentities($values['source_contact_name']); } elseif ($values['source_contact_id']) { $srcTypeImage = ""; @@ -2423,7 +2373,7 @@ public static function getContactActivitySelector(&$params) { // The first target may not be accessable to the logged in user dev/core#1052 if ($firstTargetName) { - $targetLink = CRM_Utils_System::href($firstTargetName, 'civicrm/contact/view', "reset=1&cid={$firstTargetContactID}"); + $targetLink = CRM_Utils_System::href(htmlentities($firstTargetName), 'civicrm/contact/view', "reset=1&cid={$firstTargetContactID}"); if ($showContactOverlay) { $targetTypeImage = CRM_Contact_BAO_Contact_Utils::getImage( CRM_Contact_BAO_Contact::getContactType($firstTargetContactID), @@ -2457,7 +2407,7 @@ public static function getContactActivitySelector(&$params) { foreach ($values['assignee_contact_name'] as $acID => $acName) { if ($acID && $count < 5) { $assigneeTypeImage = ""; - $assigneeLink = CRM_Utils_System::href($acName, 'civicrm/contact/view', "reset=1&cid={$acID}"); + $assigneeLink = CRM_Utils_System::href(htmlentities($acName), 'civicrm/contact/view', "reset=1&cid={$acID}"); if ($showContactOverlay) { $assigneeTypeImage = CRM_Contact_BAO_Contact_Utils::getImage( CRM_Contact_BAO_Contact::getContactType($acID), @@ -2774,18 +2724,22 @@ public static function getEntityRefFilters() { * * @param string $entityName * Always "Activity". - * @param int $entityId + * @param int|null $entityId * Id of the activity. * @throws CRM_Core_Exception */ - public static function getEntityIcon(string $entityName, int $entityId) { + public static function getEntityIcon(string $entityName, int $entityId = NULL): ?string { + $default = parent::getEntityIcon($entityName); + if (!$entityId) { + return $default; + } $field = Civi\Api4\Activity::getFields(FALSE) ->addWhere('name', '=', 'activity_type_id') ->setLoadOptions(['id', 'label', 'icon']) ->execute()->single(); $activityTypes = array_column($field['options'], NULL, 'id'); $activityType = CRM_Core_DAO::getFieldValue(parent::class, $entityId, 'activity_type_id'); - return $activityTypes[$activityType]['icon'] ?? self::$_icon; + return $activityTypes[$activityType]['icon'] ?? $default; } } diff --git a/www/modules/civicrm/CRM/Activity/DAO/Activity.php b/www/modules/civicrm/CRM/Activity/DAO/Activity.php index d24acdc68..37ea5538a 100644 --- a/www/modules/civicrm/CRM/Activity/DAO/Activity.php +++ b/www/modules/civicrm/CRM/Activity/DAO/Activity.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Activity/Activity.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:18d2e627738109617e50c084979441c0) + * (GenCodeChecksum:b7bf5b93144ffcea75b773a0893c3d5f) */ /** diff --git a/www/modules/civicrm/CRM/Activity/DAO/ActivityContact.php b/www/modules/civicrm/CRM/Activity/DAO/ActivityContact.php index 7e7ae0675..1df82812c 100644 --- a/www/modules/civicrm/CRM/Activity/DAO/ActivityContact.php +++ b/www/modules/civicrm/CRM/Activity/DAO/ActivityContact.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Activity/ActivityContact.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:1e56caf678b48133edb050534211993a) + * (GenCodeChecksum:12d0981e5fe8f5a0bb152553b009336d) */ /** diff --git a/www/modules/civicrm/CRM/Activity/Form/Activity.php b/www/modules/civicrm/CRM/Activity/Form/Activity.php index c6d0379af..1260904f2 100644 --- a/www/modules/civicrm/CRM/Activity/Form/Activity.php +++ b/www/modules/civicrm/CRM/Activity/Form/Activity.php @@ -21,6 +21,7 @@ class CRM_Activity_Form_Activity extends CRM_Contact_Form_Task { use CRM_Activity_Form_ActivityFormTrait; + use CRM_Custom_Form_CustomDataTrait; /** * The id of the object being edited / created @@ -430,16 +431,15 @@ public function preProcess() { // when custom data is included in this page $this->assign('cid', $this->_currentlyViewedContactId); - if (!empty($_POST['hidden_custom'])) { - // We need to set it in the session for the code below to work. - // CRM-3014 - // Need to assign custom data subtype to the template. - $this->set('type', 'Activity'); - $this->set('subType', $this->_activityTypeId); - $this->set('entityId', $this->_activityId); - CRM_Custom_Form_CustomData::preProcess($this, NULL, $this->_activityTypeId, 1, 'Activity', $this->_activityId); - CRM_Custom_Form_CustomData::buildQuickForm($this); - CRM_Custom_Form_CustomData::setDefaultValues($this); + if ($this->isSubmitted()) { + // The custom data fields are added to the form by an ajax form. + // However, if they are not present in the element index they will + // not be available from `$this->getSubmittedValue()` in post process. + // We do not have to set defaults or otherwise render - just add to the element index. + $this->addCustomDataFieldsToForm('Activity', array_filter([ + 'id' => $this->getActivityID(), + 'activity_type_id' => $this->_activityTypeId, + ])); } // add attachments part @@ -727,8 +727,7 @@ public function buildQuickForm() { $this->getElement('source_contact_id')->freeze(); } - //need to assign custom data type and subtype to the template - $this->assign('customDataType', 'Activity'); + //need to assign custom data subtype to the template for the initial loading of the custom data. $this->assign('customDataSubType', $this->_activityTypeId); $this->assign('entityID', $this->_activityId); @@ -910,12 +909,10 @@ public function postProcess($params = NULL) { $params['activity_type_id'] = $this->_activityTypeId; } - if (!empty($params['hidden_custom']) && !isset($params['custom'])) { - $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params, - $this->_activityId, - 'Activity' - ); - } + $params['custom'] = CRM_Core_BAO_CustomField::postProcess($this->getSubmittedValues(), + $this->_activityId, + 'Activity' + ); // format params as arrays foreach (['target', 'assignee', 'followup_assignee'] as $name) { diff --git a/www/modules/civicrm/CRM/Activity/Form/Task/PDF.php b/www/modules/civicrm/CRM/Activity/Form/Task/PDF.php index 69c1387a4..9fab52a5d 100644 --- a/www/modules/civicrm/CRM/Activity/Form/Task/PDF.php +++ b/www/modules/civicrm/CRM/Activity/Form/Task/PDF.php @@ -61,9 +61,9 @@ public function postProcess() { * * @return \Civi\Token\TokenProcessor */ - public function createTokenProcessor() { + public function createTokenProcessor(): TokenProcessor { return new TokenProcessor(\Civi::dispatcher(), [ - 'controller' => get_class(), + 'controller' => __CLASS__, 'smarty' => FALSE, 'schema' => ['activityId'], ]); @@ -76,10 +76,8 @@ public function createTokenProcessor() { * @param array $activityIds array of activity ids * @param string $html_message message text with tokens * @param array $formValues formValues from the form - * - * @return array */ - public function createDocument($activityIds, $html_message, $formValues) { + public function createDocument($activityIds, $html_message, $formValues): void { $tp = $this->createTokenProcessor(); $tp->addMessage('body_html', $html_message, 'text/html'); @@ -88,13 +86,13 @@ public function createDocument($activityIds, $html_message, $formValues) { } $tp->evaluate(); - return $this->renderFromRows($tp->getRows(), 'body_html', $formValues); + $this->renderFromRows($tp->getRows(), 'body_html', $formValues); } /** * Render html from rows * - * @param array $rows + * @param \Traversable $rows * @param string $msgPart * The name registered with the TokenProcessor * @param array $formValues diff --git a/www/modules/civicrm/CRM/Activity/Form/Task/SMS.php b/www/modules/civicrm/CRM/Activity/Form/Task/SMS.php index a90c4969e..ed4881dfd 100644 --- a/www/modules/civicrm/CRM/Activity/Form/Task/SMS.php +++ b/www/modules/civicrm/CRM/Activity/Form/Task/SMS.php @@ -19,6 +19,8 @@ */ class CRM_Activity_Form_Task_SMS extends CRM_Activity_Form_Task { + use CRM_Contact_Form_Task_SMSTrait; + /** * Are we operating in "single mode", i.e. sending sms to one * specific contact? @@ -39,7 +41,19 @@ class CRM_Activity_Form_Task_SMS extends CRM_Activity_Form_Task { */ public function preProcess() { parent::preProcess(); - CRM_Contact_Form_Task_SMSCommon::preProcessProvider($this); + $form = $this; + $this->bounceOnNoActiveProviders(); + $activityCheck = 0; + foreach ($this->_activityHolderIds as $value) { + if (CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $value, 'subject', 'id') != CRM_Contact_Form_Task_SMSCommon::RECIEVED_SMS_ACTIVITY_SUBJECT) { + $activityCheck++; + } + } + if ($activityCheck == count($this->_activityHolderIds)) { + CRM_Core_Error::statusBounce(ts("The Reply SMS Could only be sent for activities with '%1' subject.", + [1 => CRM_Contact_Form_Task_SMSCommon::RECIEVED_SMS_ACTIVITY_SUBJECT] + )); + } $this->assign('single', $this->_single); } @@ -47,26 +61,81 @@ public function preProcess() { * Build the form object. */ public function buildQuickForm() { - // Enable form element. - $this->assign('SMSTask', TRUE); - CRM_Contact_Form_Task_SMSCommon::buildQuickForm($this); + $this->buildSmsForm(); } /** - * Process the form after the input has been submitted and validated. + * Get the relevant activity name. + * + * This is likely to be further refactored/ clarified. + * + * @internal + * + * @return string */ - public function postProcess() { - CRM_Contact_Form_Task_SMSCommon::postProcess($this); + protected function getActivityName() { + return 'SMS Received'; } /** - * List available tokens for this form. - * - * @return array + * @throws \CRM_Core_Exception */ - public function listTokens() { - $tokens = CRM_Core_SelectValues::contactTokens(); - return $tokens; + protected function filterContactIDs(): void { + $form = $this; + if (!empty($this->_activityHolderIds)) { + $extendTargetContacts = 0; + $invalidActivity = 0; + $validActivities = 0; + foreach ($form->_activityHolderIds as $id) { + //valid activity check + if (CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $id, 'subject', 'id') !== $this->getActivityName()) { + $invalidActivity++; + continue; + } + + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); + $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); + //target contacts limit check + $ids = array_keys(CRM_Activity_BAO_ActivityContact::getNames($id, $targetID)); + + if (count($ids) > 1) { + $extendTargetContacts++; + continue; + } + $validActivities++; + $form->_contactIds = empty($form->_contactIds) ? $ids : array_unique(array_merge($form->_contactIds, $ids)); + } + + if (!$validActivities) { + $errorMess = ""; + if ($extendTargetContacts) { + $errorMess = ts('One selected activity consists of more than one target contact.', [ + 'count' => $extendTargetContacts, + 'plural' => '%count selected activities consist of more than one target contact.', + ]); + } + if ($invalidActivity) { + $errorMess = ($errorMess ? ' ' : ''); + $errorMess .= ts('The selected activity is invalid.', [ + 'count' => $invalidActivity, + 'plural' => '%count selected activities are invalid.', + ]); + } + CRM_Core_Error::statusBounce(ts("%1: SMS Reply will not be sent.", [1 => $errorMess])); + } + } + + //activity related variables + $form->assign('invalidActivity', $invalidActivity ?? NULL); + $form->assign('extendTargetContacts', $extendTargetContacts ?? NULL); + } + + protected function isInvalidRecipient($contactID): bool { + //to check for "if the contact id belongs to a specified activity type" + // @todo use the api instead - function is deprecated. + $actDetails = CRM_Activity_BAO_Activity::getContactActivity($contactID); + return $this->getActivityName() !== + CRM_Utils_Array::retrieveValueRecursive($actDetails, 'subject'); } } diff --git a/www/modules/civicrm/CRM/Activity/Import/Controller.php b/www/modules/civicrm/CRM/Activity/Import/Controller.php deleted file mode 100644 index 772c14620..000000000 --- a/www/modules/civicrm/CRM/Activity/Import/Controller.php +++ /dev/null @@ -1,41 +0,0 @@ -_stateMachine = new CRM_Import_StateMachine($this, $action); - - // create and instantiate the pages - $this->addPages($this->_stateMachine, $action); - - // add all the actions - $config = CRM_Core_Config::singleton(); - $this->addActions($config->uploadDir, ['uploadFile']); - } - -} diff --git a/www/modules/civicrm/CRM/Activity/Selector/Search.php b/www/modules/civicrm/CRM/Activity/Selector/Search.php index 6b2babead..34700a363 100644 --- a/www/modules/civicrm/CRM/Activity/Selector/Search.php +++ b/www/modules/civicrm/CRM/Activity/Selector/Search.php @@ -268,7 +268,7 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { if ($this->_context === 'search') { $row['checkbox'] = CRM_Core_Form::CB_PREFIX . $result->activity_id; } - $row['contact_type'] = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ? $result->contact_sub_type : $result->contact_type, FALSE, $result->contact_id + $row['contact_type'] = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ?: $result->contact_type, FALSE, $result->contact_id ); $accessMailingReport = FALSE; $activityTypeId = $row['activity_type_id']; diff --git a/www/modules/civicrm/CRM/Admin/Form.php b/www/modules/civicrm/CRM/Admin/Form.php index dad95d94a..545709698 100644 --- a/www/modules/civicrm/CRM/Admin/Form.php +++ b/www/modules/civicrm/CRM/Admin/Form.php @@ -74,7 +74,7 @@ public function preProcess() { $this->_BAOName = $this->get('BAOName'); // Otherwise, look it up from the api entity name if (!$this->_BAOName) { - $this->_BAOName = CRM_Core_DAO_AllCoreTables::getBAOClassName(CRM_Core_DAO_AllCoreTables::getFullName($this->getDefaultEntity())); + $this->_BAOName = CRM_Core_DAO_AllCoreTables::getBAOClassName(CRM_Core_DAO_AllCoreTables::getDAONameForEntity($this->getDefaultEntity())); } $this->retrieveValues(); $this->setPageTitle($this->_BAOName::getEntityTitle()); diff --git a/www/modules/civicrm/CRM/Admin/Form/CMSUser.php b/www/modules/civicrm/CRM/Admin/Form/CMSUser.php index 1d7f61458..8036ea43a 100644 --- a/www/modules/civicrm/CRM/Admin/Form/CMSUser.php +++ b/www/modules/civicrm/CRM/Admin/Form/CMSUser.php @@ -25,6 +25,15 @@ class CRM_Admin_Form_CMSUser extends CRM_Core_Form { */ public $submitOnce = TRUE; + /** + * Disable on Standalone + */ + public function preProcess() { + if (!\CRM_Utils_System::allowSynchronizeUsers()) { + \CRM_Core_Error::statusBounce(ts('This framework doesn\'t allow for syncing CMS users.')); + } + } + /** * Build the form object. */ @@ -47,7 +56,7 @@ public function buildQuickForm() { * Process the form submission. */ public function postProcess() { - $result = CRM_Utils_System::synchronizeUsers(); + $result = CRM_Utils_System::synchronizeUsersIfAllowed(); $status = ts('Checked one user record.', [ diff --git a/www/modules/civicrm/CRM/Admin/Form/MessageTemplates.php b/www/modules/civicrm/CRM/Admin/Form/MessageTemplates.php index b38f1ff5a..ea23f9711 100644 --- a/www/modules/civicrm/CRM/Admin/Form/MessageTemplates.php +++ b/www/modules/civicrm/CRM/Admin/Form/MessageTemplates.php @@ -175,11 +175,11 @@ public function buildQuickForm() { $this->assign('tokens', CRM_Utils_Token::formatTokensForDisplay($tokens)); - // if not a system message use a wysiwyg editor, CRM-5971 - if ($this->_id && - CRM_Core_DAO::getFieldValue('CRM_Core_DAO_MessageTemplate', - $this->_id, - 'workflow_id' + // if not a system message use a wysiwyg editor, CRM-5971 and dev/core#5154 + if ( + $this->_id && ( + CRM_Core_DAO::getFieldValue('CRM_Core_DAO_MessageTemplate', $this->_id, 'workflow_id') || + CRM_Core_DAO::getFieldValue('CRM_Core_DAO_MessageTemplate', $this->_id, 'workflow_name') ) ) { $this->add('textarea', 'msg_html', ts('HTML Message'), @@ -334,4 +334,12 @@ public function postProcess() { } } + /** + * Override + * @return array + */ + protected function getFieldsToExcludeFromPurification(): array { + return ['msg_html']; + } + } diff --git a/www/modules/civicrm/CRM/Admin/Form/Options.php b/www/modules/civicrm/CRM/Admin/Form/Options.php index d63a96389..65aaf7c2f 100644 --- a/www/modules/civicrm/CRM/Admin/Form/Options.php +++ b/www/modules/civicrm/CRM/Admin/Form/Options.php @@ -23,6 +23,8 @@ */ class CRM_Admin_Form_Options extends CRM_Admin_Form { + use CRM_Core_Form_EntityFormTrait; + /** * The option group name. * @@ -48,6 +50,20 @@ class CRM_Admin_Form_Options extends CRM_Admin_Form { */ public $submitOnce = TRUE; + /** + * Explicitly declare the entity api name. + */ + public function getDefaultEntity() { + return 'OptionValue'; + } + + /** + * The Option Group ID. + * @var int + * @internal + */ + protected $_gid; + /** * Pre-process */ @@ -98,13 +114,23 @@ public function preProcess() { $session->pushUserContext(CRM_Utils_System::url($url, $params)); $this->assign('id', $this->_id); - + $this->setDeleteMessage(ts('WARNING: Deleting this option will result in the loss of all %1 related records which use the option.', [1 => $this->_gLabel]) . ts('This may mean the loss of a substantial amount of data, and the action cannot be undone.') . ts('Do you want to continue?')); if ($this->_id && CRM_Core_OptionGroup::isDomainOptionGroup($this->_gName)) { $domainID = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionValue', $this->_id, 'domain_id', 'id'); if (CRM_Core_Config::domainID() != $domainID) { CRM_Core_Error::statusBounce(ts('You do not have permission to access this page.')); } } + if ($this->isSubmitted()) { + // The custom data fields are added to the form by an ajax form. + // However, if they are not present in the element index they will + // not be available from `$this->getSubmittedValue()` in post process. + // We do not have to set defaults or otherwise render - just add to the element index. + $this->addCustomDataFieldsToForm('OptionValue', array_filter([ + 'id' => $this->_id, + 'option_group_id' => $this->_gid, + ])); + } } /** @@ -156,6 +182,7 @@ public function buildQuickForm(): void { $this->setPageTitle(ts('%1 Option', [1 => $this->_gLabel])); if ($this->_action & CRM_Core_Action::DELETE) { + $this->buildDeleteForm(); return; } @@ -353,6 +380,9 @@ public function buildQuickForm(): void { } $this->addFormRule(['CRM_Admin_Form_Options', 'formRule'], $this); + //need to assign subtype to the template + $this->assign('customDataSubType', $this->_gid); + $this->assign('entityID', $this->_id); } /** @@ -472,7 +502,7 @@ public function postProcess() { } } else { - $params = $this->exportValues(); + $params = $this->getSubmittedValues(); if ($this->isGreetingOptionGroup()) { $params['filter'] = $params['contact_type_id']; } @@ -494,7 +524,10 @@ public function postProcess() { if (isset($params['color']) && strtolower($params['color']) == '#ffffff') { $params['color'] = 'null'; } - + $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params, + $this->_id, + 'OptionValue' + ); $optionValue = CRM_Core_OptionValue::addOptionValue($params, $this->_gName, $this->_action, $this->_id); CRM_Core_Session::setStatus(ts('The %1 \'%2\' has been saved.', [ @@ -520,4 +553,15 @@ protected function isGreetingOptionGroup(): bool { return in_array($this->getOptionGroupName(), ['email_greeting', 'postal_greeting', 'addressee'], TRUE); } + /** + * Override + * @return array + */ + protected function getFieldsToExcludeFromPurification(): array { + if ($this->_gName === 'from_email_address') { + return ['label']; + } + return []; + } + } diff --git a/www/modules/civicrm/CRM/Admin/Form/ScheduleReminders.php b/www/modules/civicrm/CRM/Admin/Form/ScheduleReminders.php index 0fbc86e76..62d947ad2 100644 --- a/www/modules/civicrm/CRM/Admin/Form/ScheduleReminders.php +++ b/www/modules/civicrm/CRM/Admin/Form/ScheduleReminders.php @@ -55,6 +55,13 @@ public function getDefaultEntity(): string { return 'ActionSchedule'; } + /** + * @return array + */ + protected function getFieldsToExcludeFromPurification(): array { + return ['body_html', 'html_message']; + } + /** * Because `CRM_Mailing_BAO_Mailing::commonCompose` uses different fieldNames than `CRM_Core_DAO_ActionSchedule`. * @var array @@ -179,7 +186,7 @@ public function buildQuickForm(): void { $this->add('select', 'absolute_or_relative_date', ts('When (trigger date)'), ['relative' => ts('Relative Date'), 'absolute' => ts('Choose Date')], TRUE); // SMS-only fields - $providersCount = CRM_SMS_BAO_Provider::activeProviderCount(); + $providersCount = CRM_SMS_BAO_SmsProvider::activeProviderCount(); $this->assign('sms', $providersCount); if ($providersCount) { $this->addField('mode', ['placeholder' => FALSE, 'option_url' => FALSE], TRUE)->setAttribute('class', 'crm-form-select'); @@ -190,8 +197,10 @@ public function buildQuickForm(): void { $multilingual = CRM_Core_I18n::isMultilingual(); $this->assign('multilingual', $multilingual); if ($multilingual) { - $this->addField('filter_contact_language', ['placeholder' => ts('Any language')]); - $this->addField('communication_language', ['placeholder' => 'System default language']); + $filterLanguages = \CRM_Core_BAO_ActionSchedule::getFilterContactLanguageOptions(); + $this->addField('filter_contact_language', ['placeholder' => ts('Any language'), 'options' => $filterLanguages]); + $communicationLanguages = \CRM_Core_BAO_ActionSchedule::getCommunicationLanguageOptions(); + $this->addField('communication_language', ['placeholder' => 'System default language', 'options' => $communicationLanguages]); } // Message fields diff --git a/www/modules/civicrm/CRM/Admin/Form/Setting.php b/www/modules/civicrm/CRM/Admin/Form/Setting.php index 745bd5881..154da5b91 100644 --- a/www/modules/civicrm/CRM/Admin/Form/Setting.php +++ b/www/modules/civicrm/CRM/Admin/Form/Setting.php @@ -22,6 +22,14 @@ class CRM_Admin_Form_Setting extends CRM_Core_Form { use CRM_Admin_Form_SettingTrait; + /** + * Subset of settings on the page as defined using the legacy method. + * + * @var array + * + * @deprecated - do not add new settings here - the page to display + * settings on should be defined in the setting metadata. + */ protected $_settings = []; /** diff --git a/www/modules/civicrm/CRM/Admin/Form/Setting/Case.php b/www/modules/civicrm/CRM/Admin/Form/Setting/Case.php index c952ac24d..481bd27b9 100644 --- a/www/modules/civicrm/CRM/Admin/Form/Setting/Case.php +++ b/www/modules/civicrm/CRM/Admin/Form/Setting/Case.php @@ -20,7 +20,16 @@ */ class CRM_Admin_Form_Setting_Case extends CRM_Admin_Form_Setting { + /** + * Subset of settings on the page as defined using the legacy method. + * + * @var array + * + * @deprecated - do not add new settings here - the page to display + * settings on should be defined in the setting metadata. + */ protected $_settings = [ + // @todo remove these, define any not yet defined in the setting metadata. 'civicaseRedactActivityEmail' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'civicaseAllowMultipleClients' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'civicaseNaturalActivityTypeSort' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, diff --git a/www/modules/civicrm/CRM/Admin/Form/Setting/Component.php b/www/modules/civicrm/CRM/Admin/Form/Setting/Component.php index 070e9b393..de53c3a03 100644 --- a/www/modules/civicrm/CRM/Admin/Form/Setting/Component.php +++ b/www/modules/civicrm/CRM/Admin/Form/Setting/Component.php @@ -21,7 +21,16 @@ class CRM_Admin_Form_Setting_Component extends CRM_Admin_Form_Setting { protected $_components; + /** + * Subset of settings on the page as defined using the legacy method. + * + * @var array + * + * @deprecated - do not add new settings here - the page to display + * settings on should be defined in the setting metadata. + */ protected $_settings = [ + // @todo remove these, define any not yet defined in the setting metadata. 'enable_components' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, ]; diff --git a/www/modules/civicrm/CRM/Admin/Form/Setting/Date.php b/www/modules/civicrm/CRM/Admin/Form/Setting/Date.php index a85078cd8..a773ec294 100644 --- a/www/modules/civicrm/CRM/Admin/Form/Setting/Date.php +++ b/www/modules/civicrm/CRM/Admin/Form/Setting/Date.php @@ -20,7 +20,16 @@ */ class CRM_Admin_Form_Setting_Date extends CRM_Admin_Form_Setting { - public $_settings = [ + /** + * Subset of settings on the page as defined using the legacy method. + * + * @var array + * + * @deprecated - do not add new settings here - the page to display + * settings on should be defined in the setting metadata. + */ + protected $_settings = [ + // @todo remove these, define any not yet defined in the setting metadata. 'dateformatDatetime' => CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME, 'dateformatFull' => CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME, 'dateformatPartial' => CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME, diff --git a/www/modules/civicrm/CRM/Admin/Form/Setting/Debugging.php b/www/modules/civicrm/CRM/Admin/Form/Setting/Debugging.php index a328af40c..fe4e7676d 100644 --- a/www/modules/civicrm/CRM/Admin/Form/Setting/Debugging.php +++ b/www/modules/civicrm/CRM/Admin/Form/Setting/Debugging.php @@ -20,7 +20,16 @@ */ class CRM_Admin_Form_Setting_Debugging extends CRM_Admin_Form_Setting { + /** + * Subset of settings on the page as defined using the legacy method. + * + * @var array + * + * @deprecated - do not add new settings here - the page to display + * settings on should be defined in the setting metadata. + */ protected $_settings = [ + // @todo remove these, define any not yet defined in the setting metadata. 'debug_enabled' => CRM_Core_BAO_Setting::DEVELOPER_PREFERENCES_NAME, 'backtrace' => CRM_Core_BAO_Setting::DEVELOPER_PREFERENCES_NAME, 'fatalErrorHandler' => CRM_Core_BAO_Setting::DEVELOPER_PREFERENCES_NAME, diff --git a/www/modules/civicrm/CRM/Admin/Form/Setting/Localization.php b/www/modules/civicrm/CRM/Admin/Form/Setting/Localization.php index 6d4f6ac70..28a3198ba 100644 --- a/www/modules/civicrm/CRM/Admin/Form/Setting/Localization.php +++ b/www/modules/civicrm/CRM/Admin/Form/Setting/Localization.php @@ -23,7 +23,16 @@ */ class CRM_Admin_Form_Setting_Localization extends CRM_Admin_Form_Setting { + /** + * Subset of settings on the page as defined using the legacy method. + * + * @var array + * + * @deprecated - do not add new settings here - the page to display + * settings on should be defined in the setting metadata. + */ protected $_settings = [ + // @todo remove these, define any not yet defined in the setting metadata. 'contact_default_language' => CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME, 'countryLimit' => CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME, 'customTranslateFunction' => CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME, diff --git a/www/modules/civicrm/CRM/Admin/Form/Setting/Mail.php b/www/modules/civicrm/CRM/Admin/Form/Setting/Mail.php index 358c87f81..c3392067f 100644 --- a/www/modules/civicrm/CRM/Admin/Form/Setting/Mail.php +++ b/www/modules/civicrm/CRM/Admin/Form/Setting/Mail.php @@ -20,7 +20,16 @@ */ class CRM_Admin_Form_Setting_Mail extends CRM_Admin_Form_Setting { + /** + * Subset of settings on the page as defined using the legacy method. + * + * @var array + * + * @deprecated - do not add new settings here - the page to display + * settings on should be defined in the setting metadata. + */ protected $_settings = [ + // @todo remove these, define any not yet defined in the setting metadata. 'mailerBatchLimit' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, 'mailThrottleTime' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, 'mailerJobSize' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, diff --git a/www/modules/civicrm/CRM/Admin/Form/Setting/Mapping.php b/www/modules/civicrm/CRM/Admin/Form/Setting/Mapping.php index 69b7fb7c9..99a674b96 100644 --- a/www/modules/civicrm/CRM/Admin/Form/Setting/Mapping.php +++ b/www/modules/civicrm/CRM/Admin/Form/Setting/Mapping.php @@ -20,7 +20,16 @@ */ class CRM_Admin_Form_Setting_Mapping extends CRM_Admin_Form_Setting { + /** + * Subset of settings on the page as defined using the legacy method. + * + * @var array + * + * @deprecated - do not add new settings here - the page to display + * settings on should be defined in the setting metadata. + */ protected $_settings = [ + // @todo remove these, define any not yet defined in the setting metadata. 'mapAPIKey' => CRM_Core_BAO_Setting::MAP_PREFERENCES_NAME, 'mapProvider' => CRM_Core_BAO_Setting::MAP_PREFERENCES_NAME, 'geoAPIKey' => CRM_Core_BAO_Setting::MAP_PREFERENCES_NAME, diff --git a/www/modules/civicrm/CRM/Admin/Form/Setting/Miscellaneous.php b/www/modules/civicrm/CRM/Admin/Form/Setting/Miscellaneous.php index 76c80cf4f..62bc2c30b 100644 --- a/www/modules/civicrm/CRM/Admin/Form/Setting/Miscellaneous.php +++ b/www/modules/civicrm/CRM/Admin/Form/Setting/Miscellaneous.php @@ -20,7 +20,16 @@ */ class CRM_Admin_Form_Setting_Miscellaneous extends CRM_Admin_Form_Setting { + /** + * Subset of settings on the page as defined using the legacy method. + * + * @var array + * + * @deprecated - do not add new settings here - the page to display + * settings on should be defined in the setting metadata. + */ protected $_settings = [ + // @todo remove these, define any not yet defined in the setting metadata. 'max_attachments' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'max_attachments_backend' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'contact_undelete' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, diff --git a/www/modules/civicrm/CRM/Admin/Form/Setting/Path.php b/www/modules/civicrm/CRM/Admin/Form/Setting/Path.php index e30462e34..e728319ec 100644 --- a/www/modules/civicrm/CRM/Admin/Form/Setting/Path.php +++ b/www/modules/civicrm/CRM/Admin/Form/Setting/Path.php @@ -20,7 +20,16 @@ */ class CRM_Admin_Form_Setting_Path extends CRM_Admin_Form_Setting { + /** + * Subset of settings on the page as defined using the legacy method. + * + * @var array + * + * @deprecated - do not add new settings here - the page to display + * settings on should be defined in the setting metadata. + */ protected $_settings = [ + // @todo remove these, define any not yet defined in the setting metadata. 'uploadDir' => CRM_Core_BAO_Setting::DIRECTORY_PREFERENCES_NAME, 'imageUploadDir' => CRM_Core_BAO_Setting::DIRECTORY_PREFERENCES_NAME, 'customFileUploadDir' => CRM_Core_BAO_Setting::DIRECTORY_PREFERENCES_NAME, diff --git a/www/modules/civicrm/CRM/Admin/Form/Setting/Smtp.php b/www/modules/civicrm/CRM/Admin/Form/Setting/Smtp.php index 6670e4a63..fe967d919 100644 --- a/www/modules/civicrm/CRM/Admin/Form/Setting/Smtp.php +++ b/www/modules/civicrm/CRM/Admin/Form/Setting/Smtp.php @@ -20,7 +20,17 @@ */ class CRM_Admin_Form_Setting_Smtp extends CRM_Admin_Form_Setting { protected $_testButtonName; + + /** + * Subset of settings on the page as defined using the legacy method. + * + * @var array + * + * @deprecated - do not add new settings here - the page to display + * settings on should be defined in the setting metadata. + */ protected $_settings = [ + // @todo remove these, define any not yet defined in the setting metadata. 'allow_mail_from_logged_in_contact' => CRM_Core_BAO_Setting::DIRECTORY_PREFERENCES_NAME, ]; @@ -120,9 +130,6 @@ public function postProcess() { $to = '"' . $toDisplayName . '"' . "<$toEmail>"; $from = '"' . $domainEmailName . '" <' . $domainEmailAddress . '>'; - $testMailStatusMsg = ts('Sending test email') . ':
' - . ts('From: %1', [1 => $domainEmailAddress]) . '
' - . ts('To: %1', [1 => $toEmail]) . '
'; $params = []; if ($formValues['outBound_option'] == CRM_Mailing_Config::OUTBOUND_OPTION_SMTP) { @@ -163,32 +170,16 @@ public function postProcess() { $mailerName = 'mail'; } - $headers = [ - 'From' => $from, - 'To' => $to, - 'Subject' => $subject, + $mailParams = [ + 'from' => $from, + 'to' => $to, + 'subject' => $subject, + 'text' => $message, + 'toEmail' => $toEmail, ]; $mailer = CRM_Utils_Mail::_createMailer($mailerName, $params); - - try { - $mailer->send($toEmail, $headers, $message); - if (defined('CIVICRM_MAIL_LOG') && defined('CIVICRM_MAIL_LOG_AND_SEND')) { - $testMailStatusMsg .= '
' . ts('You have defined CIVICRM_MAIL_LOG_AND_SEND - mail will be logged.') . '

'; - } - if (defined('CIVICRM_MAIL_LOG') && !defined('CIVICRM_MAIL_LOG_AND_SEND')) { - CRM_Core_Session::setStatus($testMailStatusMsg . ts('You have defined CIVICRM_MAIL_LOG - no mail will be sent. Your %1 settings have not been tested.', [1 => strtoupper($mailerName)]), ts("Mail not sent"), "warning"); - } - else { - CRM_Core_Session::setStatus($testMailStatusMsg . ts('Your %1 settings are correct. A test email has been sent to your email address.', [1 => strtoupper($mailerName)]), ts("Mail Sent"), "success"); - } - } - catch (Exception $e) { - $result = $e; - Civi::log()->error($e->getMessage()); - $errorMessage = CRM_Utils_Mail::errorMessage($mailer, $result); - CRM_Core_Session::setStatus($testMailStatusMsg . ts('Oops. Your %1 settings are incorrect. No test mail has been sent.', [1 => strtoupper($mailerName)]) . $errorMessage, ts("Mail Not Sent"), "error"); - } + CRM_Utils_Mail::sendTest($mailer, $mailParams); } } diff --git a/www/modules/civicrm/CRM/Admin/Form/Setting/UF.php b/www/modules/civicrm/CRM/Admin/Form/Setting/UF.php index 041a3f5ff..4ab621792 100644 --- a/www/modules/civicrm/CRM/Admin/Form/Setting/UF.php +++ b/www/modules/civicrm/CRM/Admin/Form/Setting/UF.php @@ -20,8 +20,6 @@ */ class CRM_Admin_Form_Setting_UF extends CRM_Admin_Form_Setting { - protected $_settings = []; - protected $_uf = NULL; /** diff --git a/www/modules/civicrm/CRM/Admin/Form/Setting/Url.php b/www/modules/civicrm/CRM/Admin/Form/Setting/Url.php index 85151cbe7..6d4463219 100644 --- a/www/modules/civicrm/CRM/Admin/Form/Setting/Url.php +++ b/www/modules/civicrm/CRM/Admin/Form/Setting/Url.php @@ -19,9 +19,21 @@ * This class generates form components for Site Url. */ class CRM_Admin_Form_Setting_Url extends CRM_Admin_Form_Setting { + + /** + * Subset of settings on the page as defined using the legacy method. + * + * @var array + * + * @deprecated - do not add new settings here - the page to display + * settings on should be defined in the setting metadata. + */ protected $_settings = [ + // @todo remove these, define any not yet defined in the setting metadata. 'disable_core_css' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'defaultExternUrl' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'enableSSL' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'verifySSL' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'userFrameworkResourceURL' => CRM_Core_BAO_Setting::URL_PREFERENCES_NAME, 'imageUploadURL' => CRM_Core_BAO_Setting::URL_PREFERENCES_NAME, 'customCSSURL' => CRM_Core_BAO_Setting::URL_PREFERENCES_NAME, @@ -33,15 +45,6 @@ class CRM_Admin_Form_Setting_Url extends CRM_Admin_Form_Setting { */ public function buildQuickForm() { $this->setTitle(ts('Settings - Resource URLs')); - $settingFields = civicrm_api('setting', 'getfields', [ - 'version' => 3, - ]); - - $this->addYesNo('enableSSL', ts('Force Secure URLs (SSL)')); - $this->addYesNo('verifySSL', ts('Verify SSL Certs')); - // FIXME: verifySSL should use $_settings instead of manually adding fields - $this->assign('verifySSL_description', $settingFields['values']['verifySSL']['description']); - $this->addFormRule(['CRM_Admin_Form_Setting_Url', 'formRule']); parent::buildQuickForm(); diff --git a/www/modules/civicrm/CRM/Admin/Page/AJAX.php b/www/modules/civicrm/CRM/Admin/Page/AJAX.php index 5552495c4..0151966b5 100644 --- a/www/modules/civicrm/CRM/Admin/Page/AJAX.php +++ b/www/modules/civicrm/CRM/Admin/Page/AJAX.php @@ -241,7 +241,21 @@ public static function getStatusMsg() { case 'CRM_Contact_BAO_Group': $ret['content'] = ts('Are you sure you want to disable this Group?'); - $ret['content'] .= '

' . ts('WARNING - Disabling this group will disable all the child groups associated if any.') . ''; + $sgContent = ''; + $sgReferencingThisGroup = CRM_Contact_BAO_SavedSearch::getSmartGroupsUsingGroup($recordID); + if (!empty($sgReferencingThisGroup)) { + $sgContent .= '

' . ts('WARNING - This Group is currently referenced by %1 smart group(s).', [ + 1 => count($sgReferencingThisGroup), + ]) . '
    '; + foreach ($sgReferencingThisGroup as $gid => $group) { + $sgContent .= '
  • ' . ts('%1 Edit Smart Group Criteria', [ + 1 => $group['title'], + 2 => $group['editSearchURL'], + ]) . '
  • '; + } + $sgContent .= '
' . ts('Disabling this group will cause these groups to no longer restrict members based on membership in this group. Please edit and remove this group as a criteria from these smart groups.'); + } + $ret['content'] .= $sgContent . '

' . ts('WARNING - Disabling this group will disable all the child groups associated if any.') . ''; break; case 'CRM_Core_BAO_OptionGroup': @@ -337,7 +351,7 @@ public static function getTagTree() { 'is_selectable' => (bool) $dao->is_selectable, 'is_reserved' => (bool) $dao->is_reserved, 'used_for' => $usedFor, - 'color' => $dao->color ? $dao->color : '#ffffff', + 'color' => $dao->color ?: '#ffffff', 'usages' => civicrm_api3('EntityTag', 'getcount', [ 'entity_table' => ['IN' => $usedFor], 'tag_id' => $dao->id, diff --git a/www/modules/civicrm/CRM/Admin/Page/Extensions.php b/www/modules/civicrm/CRM/Admin/Page/Extensions.php index d04ad328e..27505fa06 100644 --- a/www/modules/civicrm/CRM/Admin/Page/Extensions.php +++ b/www/modules/civicrm/CRM/Admin/Page/Extensions.php @@ -270,7 +270,7 @@ public function formatRemoteExtensionRows($localExtensionRows) { ); if (isset($localExtensionRows[$info->key])) { if (array_key_exists('version', $localExtensionRows[$info->key])) { - if (version_compare($localExtensionRows[$info->key]['version'], $info->version, '<')) { + if (version_compare($localExtensionRows[$info->key]['version'] ?? '', $info->version, '<')) { $row['upgradelink'] = $mapper->getUpgradeLink($remoteExtensions[$info->key], $localExtensionRows[$info->key]); } } @@ -361,6 +361,7 @@ private static function fillMissingInfoKeys(array $info) { 'comments' => FALSE, ]; $info = array_merge($defaultKeys, $info); + $info['is_stable'] = $info['develStage'] === 'stable' && !preg_match(";(alpha|beta|dev);", $info['version']); foreach ($info['authors'] as &$author) { $author = array_merge(['homepage' => ''], $author); } diff --git a/www/modules/civicrm/CRM/Admin/Page/MessageTemplates.php b/www/modules/civicrm/CRM/Admin/Page/MessageTemplates.php index 1de19bfc7..e50a43033 100644 --- a/www/modules/civicrm/CRM/Admin/Page/MessageTemplates.php +++ b/www/modules/civicrm/CRM/Admin/Page/MessageTemplates.php @@ -260,6 +260,10 @@ public function browse() { $messageTemplate->find(); while ($messageTemplate->fetch()) { $values[$messageTemplate->id] = ['class' => '']; + // Make the subject a empty string if it isn't defined + if (!isset($messageTemplate->msg_subject)) { + $messageTemplate->msg_subject = ""; + } CRM_Core_DAO::storeValues($messageTemplate, $values[$messageTemplate->id]); // populate action links $this->action($messageTemplate, $action, $values[$messageTemplate->id], $links, CRM_Core_Permission::EDIT); @@ -281,6 +285,7 @@ public function browse() { $this->assign('canEditSystemTemplates', CRM_Core_Permission::check('edit system workflow message templates')); $this->assign('canEditMessageTemplates', CRM_Core_Permission::check('edit message templates')); $this->assign('canEditUserDrivenMessageTemplates', CRM_Core_Permission::check('edit user-driven message templates')); + $this->assign('isCiviMailEnabled', CRM_Core_Component::isEnabled('CiviMail')); Civi::resources() ->addScriptFile('civicrm', 'templates/CRM/common/TabHeader.js', 1, 'html-header') ->addSetting([ diff --git a/www/modules/civicrm/CRM/Admin/Page/Options.php b/www/modules/civicrm/CRM/Admin/Page/Options.php index 19954130e..a092c0663 100644 --- a/www/modules/civicrm/CRM/Admin/Page/Options.php +++ b/www/modules/civicrm/CRM/Admin/Page/Options.php @@ -83,6 +83,8 @@ public function preProcess() { } // If we don't have a group we will browse all groups if (!self::$_gName) { + // Ensure that gName is assigned to the template to prevent smarty notice. + $this->assign('gName'); return; } $this->set('gName', self::$_gName); diff --git a/www/modules/civicrm/CRM/Badge/BAO/Layout.php b/www/modules/civicrm/CRM/Badge/BAO/Layout.php index b41486847..6d2bf4545 100644 --- a/www/modules/civicrm/CRM/Badge/BAO/Layout.php +++ b/www/modules/civicrm/CRM/Badge/BAO/Layout.php @@ -100,6 +100,7 @@ public static function del($printLabelId) { */ public static function getList() { $printLabel = new CRM_Core_DAO_PrintLabel(); + $printLabel->is_active = 1; $printLabel->find(); $labels = []; diff --git a/www/modules/civicrm/CRM/Batch/DAO/Batch.php b/www/modules/civicrm/CRM/Batch/DAO/Batch.php index b0731fcf1..e35876b29 100644 --- a/www/modules/civicrm/CRM/Batch/DAO/Batch.php +++ b/www/modules/civicrm/CRM/Batch/DAO/Batch.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Batch/Batch.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:9b0fed60412874a7e3ebcaabe143bb18) + * (GenCodeChecksum:bb8185601a849ffb9d02740709d60674) */ /** diff --git a/www/modules/civicrm/CRM/Batch/DAO/EntityBatch.php b/www/modules/civicrm/CRM/Batch/DAO/EntityBatch.php index 3f49ec82b..015ee6fa3 100644 --- a/www/modules/civicrm/CRM/Batch/DAO/EntityBatch.php +++ b/www/modules/civicrm/CRM/Batch/DAO/EntityBatch.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Batch/EntityBatch.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:c2622d64e4cab3d7fb2a2b1cc2424efd) + * (GenCodeChecksum:1a1671244f5bfc0adb9c7c5e4aa80b50) */ /** diff --git a/www/modules/civicrm/CRM/Batch/Form/Entry.php b/www/modules/civicrm/CRM/Batch/Form/Entry.php index 01b40c1da..50fa0f531 100644 --- a/www/modules/civicrm/CRM/Batch/Form/Entry.php +++ b/www/modules/civicrm/CRM/Batch/Form/Entry.php @@ -22,13 +22,6 @@ */ class CRM_Batch_Form_Entry extends CRM_Core_Form { - /** - * Maximum profile fields that will be displayed. - * - * @var int - */ - protected $_rowCount = 1; - /** * Batch id. * @@ -71,13 +64,6 @@ class CRM_Batch_Form_Entry extends CRM_Core_Form { */ protected $currentRowIsRenewOption; - /** - * Contact fields. - * - * @var array - */ - protected $_contactFields = []; - /** * Fields array of fields in the batch profile. * @@ -135,7 +121,7 @@ public function setCurrentRowContributionID(int $currentRowContributionID): void } /** - * @return mixed + * @return int */ public function getCurrentRowMembershipID() { return $this->currentRowMembershipID; @@ -207,7 +193,7 @@ public function preProcess() { $this->assign('batchTotal', !empty($this->_batchInfo['total']) ? $this->_batchInfo['total'] : NULL); $this->assign('batchType', $this->_batchInfo['type_id']); - // get the profile id associted with this batch type + // get the profile id associated with this batch type $this->_profileId = CRM_Batch_BAO_Batch::getProfileId($this->_batchInfo['type_id']); } CRM_Core_Resources::singleton() @@ -239,7 +225,7 @@ public function setBatchID($id) { * * @throws \CRM_Core_Exception */ - public function buildQuickForm() { + public function buildQuickForm(): void { if (!$this->_profileId) { CRM_Core_Error::statusBounce(ts('Profile for bulk data entry is missing.')); } @@ -260,14 +246,7 @@ public function buildQuickForm() { $this->_fields = CRM_Core_BAO_UFGroup::getFields($this->_profileId, FALSE, CRM_Core_Action::VIEW); - // remove file type field and then limit fields - $suppressFields = FALSE; foreach ($this->_fields as $name => $field) { - if ($cfID = CRM_Core_BAO_CustomField::getKeyID($name) && $this->_fields[$name]['html_type'] == 'Autocomplete-Select') { - $suppressFields = TRUE; - unset($this->_fields[$name]); - } - //fix to reduce size as we are using this field in grid if (is_array($field['attributes']) && $this->_fields[$name]['attributes']['size'] > 19) { //shrink class to "form-text-medium" @@ -321,7 +300,7 @@ public function buildQuickForm() { 'placeholder' => ts('- select -'), ]); - // special field specific to membership batch udpate + // special field specific to membership batch update if ($this->_batchInfo['type_id'] == 2) { $options = [ 1 => ts('Add Membership'), @@ -350,7 +329,7 @@ public function buildQuickForm() { $this->add('select', "open_pledges[$rowNumber]", '', $options); } - foreach ($this->_fields as $name => $field) { + foreach ($this->_fields as $field) { if (in_array($field['field_type'], $contactTypes)) { $fld = explode('-', $field['name']); $contactReturnProperties[$field['name']] = $fld[0]; @@ -385,10 +364,6 @@ public function buildQuickForm() { // don't set the status message when form is submitted. $buttonName = $this->controller->getButtonName('submit'); - - if ($suppressFields && $buttonName != '_qf_Entry_next') { - CRM_Core_Session::setStatus(ts("File type field(s) in the selected profile are not supported for Update multiple records."), ts('Some Fields Excluded'), 'info'); - } } /** @@ -403,6 +378,7 @@ public function buildQuickForm() { * * @return array * list of errors to be posted back to the form + * @throws \CRM_Core_Exception */ public static function formRule($params, $files, $self) { $errors = []; @@ -436,7 +412,7 @@ public static function formRule($params, $files, $self) { $batchTotal = 0; foreach ($params['field'] as $key => $value) { - $batchTotal += ($value['total_amount'] ?: 0); + $batchTotal += (float) (CRM_Utils_Rule::cleanMoney($value['total_amount'] ?: 0)); //validate for soft credit fields if (!empty($params['soft_credit_contact_id'][$key]) && empty($params['soft_credit_amount'][$key])) { @@ -566,7 +542,7 @@ public function postProcess() { * * @throws \CRM_Core_Exception */ - private function processContribution(&$params) { + private function processContribution(array &$params): bool { foreach ($this->submittableMoneyFields as $moneyField) { foreach ($params['field'] as $index => $fieldValues) { @@ -703,8 +679,8 @@ private function processContribution(&$params) { if (is_numeric($pledgeId)) { $result = CRM_Pledge_BAO_PledgePayment::getPledgePayments($pledgeId); $pledgePaymentId = 0; - foreach ($result as $key => $values) { - if ($values['status'] != 'Completed') { + foreach ($result as $values) { + if ($values['status'] !== 'Completed') { $pledgePaymentId = $values['id']; break; } @@ -771,7 +747,7 @@ private function processMembership(array $params) { $batchTotal = 0; // get the price set associated with offline membership $priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', 'default_membership_type_amount', 'id', 'name'); - $this->_priceSet = $priceSets = current(CRM_Price_BAO_PriceSet::getSetDetail($priceSetId)); + $this->_priceSet = current(CRM_Price_BAO_PriceSet::getSetDetail($priceSetId)); if (isset($params['field'])) { // @todo - most of the wrangling in this function is because the api is not being used, especially date stuff. @@ -868,7 +844,7 @@ private function processMembership(array $params) { 'total_amount' => $order->getTotalAmount() + $order->getTotalTaxAmount(), 'check_number' => $this->currentRow['check_number'] ?? '', 'trxn_date' => $this->currentRow['receive_date'], - 'trxn_id' => $this->currentRow['trxn_id'], + 'trxn_id' => $this->currentRow['trxn_id'] ?? '', 'payment_instrument_id' => $this->currentRow['payment_instrument_id'], 'contribution_id' => $this->getCurrentRowContributionID(), 'is_send_contribution_notification' => FALSE, @@ -885,13 +861,10 @@ private function processMembership(array $params) { //process premiums if (!empty($value['product_name'])) { if ($value['product_name'][0] > 0) { - [$products, $options] = CRM_Contribute_BAO_Premium::getPremiumProductInfo(); + [, $options] = CRM_Contribute_BAO_Premium::getPremiumProductInfo(); $value['hidden_Premium'] = 1; - $value['product_option'] = CRM_Utils_Array::value( - $value['product_name'][1], - $options[$value['product_name'][0]] - ); + $value['product_option'] = $options[$value['product_name'][0]][$value['product_name'][1]] ?? NULL; $premiumParams = [ 'product_id' => $value['product_name'][0], @@ -941,11 +914,11 @@ protected function emailReceipt($form, &$formValues): bool { if (!empty($formValues['contribution_status_id'])) { $form->assign('contributionStatusID', $formValues['contribution_status_id']); - $form->assign('contributionStatus', CRM_Contribute_PseudoConstant::contributionStatus($formValues['contribution_status_id'], 'name')); + $form->assign('contributionStatus', CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $formValues['contribution_status_id'])); } $form->assign('receiptType', $this->currentRowIsRenew() ? 'membership renewal' : 'membership signup'); - $form->assign('receive_date', CRM_Utils_Array::value('receive_date', $formValues)); + $form->assign('receive_date', $formValues['receive_date'] ?? NULL); $form->assign('formValues', $formValues); $form->assign('mem_start_date', CRM_Utils_Date::formatDateOnlyLong($membership->start_date)); @@ -988,6 +961,9 @@ protected function emailReceipt($form, &$formValues): bool { * * @param array $value * Associated array of submitted values. + * + * @throws \CRM_Core_Exception + * @throws \Civi\API\Exception\UnauthorizedException */ private function updateContactInfo(array &$value) { $value['preserveDBName'] = $this->_preserveDefault; @@ -1008,7 +984,8 @@ private function updateContactInfo(array &$value) { * * @param array $params * - * @return bool + * @return float + * @throws \CRM_Core_Exception */ public function testProcessMembership($params) { return $this->processMembership($params); @@ -1189,7 +1166,7 @@ private function currentRowIsRenew(): bool { */ protected function getCurrentMembership() { if (!isset($this->currentRowExistingMembership)) { - // CRM-7297 - allow membership type to be be changed during renewal so long as the parent org of new membershipType + // CRM-7297 - allow membership type to be changed during renewal so long as the parent org of new membershipType // is the same as the parent org of an existing membership of the contact $this->currentRowExistingMembership = CRM_Member_BAO_Membership::getContactMembership($this->getCurrentRowContactID(), $this->getCurrentRowMembershipTypeID(), FALSE, NULL, TRUE @@ -1206,6 +1183,7 @@ protected function getCurrentMembership() { * Get the params as related to the membership entity. * * @return array + * @throws \CRM_Core_Exception */ private function getCurrentRowMembershipParams(): array { return array_merge($this->getCurrentRowCustomParams(), [ diff --git a/www/modules/civicrm/CRM/Campaign/BAO/Survey.php b/www/modules/civicrm/CRM/Campaign/BAO/Survey.php index 9976b33dc..10b529230 100644 --- a/www/modules/civicrm/CRM/Campaign/BAO/Survey.php +++ b/www/modules/civicrm/CRM/Campaign/BAO/Survey.php @@ -159,14 +159,13 @@ public static function getSurveyActivityType($returnColumn = 'label', $includePe } /** - * Get Surveys custom groups. + * @deprecated since 5.71 will be removed around 5.85. * * @param array $surveyTypes - * an array of survey type id. - * * @return array */ public static function getSurveyCustomGroups($surveyTypes = []) { + CRM_Core_Error::deprecatedFunctionWarning('API'); $customGroups = []; if (!is_array($surveyTypes)) { $surveyTypes = [$surveyTypes]; @@ -311,7 +310,7 @@ public static function voterDetails($voterIds, $returnProperties = []) { foreach ($returnProperties as $property => $ignore) { $voterDetails[$contact->contactId][$property] = $contact->$property; } - $image = CRM_Contact_BAO_Contact_Utils::getImage($contact->contact_sub_type ? $contact->contact_sub_type : $contact->contact_type, + $image = CRM_Contact_BAO_Contact_Utils::getImage($contact->contact_sub_type ?: $contact->contact_type, FALSE, $contact->contactId ); diff --git a/www/modules/civicrm/CRM/Campaign/DAO/Campaign.php b/www/modules/civicrm/CRM/Campaign/DAO/Campaign.php index e30faa115..2beb14bc1 100644 --- a/www/modules/civicrm/CRM/Campaign/DAO/Campaign.php +++ b/www/modules/civicrm/CRM/Campaign/DAO/Campaign.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Campaign/Campaign.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:a54430a3a190945a3252b69151eb26ff) + * (GenCodeChecksum:3115ee8f5bd37452063eca7928cd7af3) */ /** diff --git a/www/modules/civicrm/CRM/Campaign/DAO/CampaignGroup.php b/www/modules/civicrm/CRM/Campaign/DAO/CampaignGroup.php index f57f680fa..badbc5298 100644 --- a/www/modules/civicrm/CRM/Campaign/DAO/CampaignGroup.php +++ b/www/modules/civicrm/CRM/Campaign/DAO/CampaignGroup.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Campaign/CampaignGroup.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:f76990e9e90197859805296c8ce7818e) + * (GenCodeChecksum:7d0f19548e33f52e85689ce7e61f1969) */ /** diff --git a/www/modules/civicrm/CRM/Campaign/DAO/Survey.php b/www/modules/civicrm/CRM/Campaign/DAO/Survey.php index 8e083d7d8..3bbf118cc 100644 --- a/www/modules/civicrm/CRM/Campaign/DAO/Survey.php +++ b/www/modules/civicrm/CRM/Campaign/DAO/Survey.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Campaign/Survey.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:6b7ed7c668ff78b88eff06644a9673c8) + * (GenCodeChecksum:18f75c5ca402c23c569b37f7a2438b12) */ /** diff --git a/www/modules/civicrm/CRM/Campaign/Form/Campaign.php b/www/modules/civicrm/CRM/Campaign/Form/Campaign.php index 441033281..e9eabf0db 100644 --- a/www/modules/civicrm/CRM/Campaign/Form/Campaign.php +++ b/www/modules/civicrm/CRM/Campaign/Form/Campaign.php @@ -19,6 +19,29 @@ * This class generates form components for processing a campaign. */ class CRM_Campaign_Form_Campaign extends CRM_Core_Form { + use CRM_Custom_Form_CustomDataTrait; + use CRM_Campaign_Form_CampaignFormTrait; + + /** + * Fields for the entity to be assigned to the template. + * + * Note this form is not implementing the EntityFormTrait but + * is following it's syntax for consistency. + * + * Fields may have keys + * - name (required to show in tpl from the array) + * - description (optional, will appear below the field) + * - not-auto-addable - this class will not attempt to add the field using addField. + * (this will be automatically set if the field does not have html in it's metadata + * or is not a core field on the form's entity). + * - help (option) add help to the field - e.g ['id' => 'id-source', 'file' => 'CRM/Contact/Form/Contact']] + * - template - use a field specific template to render this field + * - required + * - is_freeze (field should be frozen). + * + * @var array + */ + protected array $entityFields = []; /** * Action @@ -35,31 +58,30 @@ class CRM_Campaign_Form_Campaign extends CRM_Core_Form { protected $_context; /** - * Object values. - * - * @var array - */ - protected $_values; - - /** - * The id of the campaign we are proceessing + * The id of the campaign we are processing * * @var int + * + * @deprecated use getCampaignID() */ protected $_campaignId; /** * Explicitly declare the entity api name. */ - public function getDefaultEntity() { + public function getDefaultEntity(): string { return 'Campaign'; } - public function preProcess() { + /** + * @throws \CRM_Core_Exception + */ + public function preProcess(): void { if (!CRM_Campaign_BAO_Campaign::accessCampaign()) { CRM_Utils_System::permissionDenied(); } + $this->setEntityFields(); $this->_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this); $this->assign('context', $this->_context); @@ -82,60 +104,57 @@ public function preProcess() { $session->pushUserContext(CRM_Utils_System::url('civicrm/campaign', 'reset=1&subPage=campaign')); $this->assign('action', $this->_action); - //load the values; - $this->_values = $this->get('values'); - if (!is_array($this->_values)) { - $this->_values = []; - - // if we are editing - if (isset($this->_campaignId) && $this->_campaignId) { - $params = ['id' => $this->_campaignId]; - CRM_Campaign_BAO_Campaign::retrieve($params, $this->_values); - } - - //lets use current object session. - $this->set('values', $this->_values); + if ($this->isSubmitted()) { + // The custom data fields are added to the form by an ajax form. + // However, if they are not present in the element index they will + // not be available from `$this->getSubmittedValue()` in post process. + // We do not have to set defaults or otherwise render - just add to the element index. + $this->addCustomDataFieldsToForm('Campaign', array_filter([ + 'id' => $this->getCampaignID(), + 'campaign_type_id' => $this->getSubmittedValue('campaign_type_id'), + ])); } + } - // when custom data is included in form. - if (!empty($_POST['hidden_custom'])) { - $campaignTypeId = empty($_POST['campaign_type_id']) ? NULL : $_POST['campaign_type_id']; - $this->set('type', 'Campaign'); - $this->set('subType', $campaignTypeId); - $this->set('entityId', $this->_campaignId); - - CRM_Custom_Form_CustomData::preProcess($this, NULL, $campaignTypeId, 1, 'Campaign', $this->_campaignId); - CRM_Custom_Form_CustomData::buildQuickForm($this); - CRM_Custom_Form_CustomData::setDefaultValues($this); - } + /** + * Set entity fields to be assigned to the form. + */ + protected function setEntityFields(): void { + $this->entityFields = [ + 'title' => ['name' => 'title'], + 'description' => ['name' => 'description'], + 'start_date' => ['name' => 'start_date', 'default' => date('Y-m-d H:i:s')], + 'end_date' => ['name' => 'start_date'], + 'campaign_type_id' => ['name' => 'campaign_type_id'], + 'status_id' => ['name' => 'status_id'], + 'goal_general' => ['name' => 'goal_general'], + 'goal_revenue' => ['name' => 'goal_revenue'], + 'external_identifier' => ['name' => 'external_identifier'], + 'is_active' => ['name' => 'is_active', 'default' => 1], + ]; } /** * Set default values for the form. Note that in edit/view mode * the default values are retrieved from the database * - * * @return array + * @throws \CRM_Core_Exception */ - public function setDefaultValues() { - $defaults = $this->_values; - - if (empty($defaults['start_date'])) { - $defaults['start_date'] = date('Y-m-d H:i:s'); + public function setDefaultValues(): array { + $defaults = []; + foreach ($this->entityFields as $field) { + $defaults[$field['name']] = $this->getCampaignValue($field['name']) ?? ($field['default'] ?? ''); } - if (!isset($defaults['is_active'])) { - $defaults['is_active'] = 1; - } - - if (!$this->_campaignId) { + if (!$this->getCampaignID()) { return $defaults; } $dao = new CRM_Campaign_DAO_CampaignGroup(); $campaignGroups = []; - $dao->campaign_id = $this->_campaignId; + $dao->campaign_id = $this->getCampaignID(); $dao->find(); while ($dao->fetch()) { @@ -148,8 +167,11 @@ public function setDefaultValues() { return $defaults; } - public function buildQuickForm() { - $this->add('hidden', 'id', $this->_campaignId); + /** + * @throws \CRM_Core_Exception + */ + public function buildQuickForm(): void { + $this->add('hidden', 'id', $this->getCampaignID()); if ($this->_action & CRM_Core_Action::DELETE) { $this->addButtons([ @@ -168,29 +190,17 @@ public function buildQuickForm() { $this->applyFilter('__ALL__', 'trim'); - //lets assign custom data type and subtype. - $this->assign('customDataType', 'Campaign'); - $this->assign('entityID', $this->_campaignId); - $this->assign('customDataSubType', CRM_Utils_Array::value('campaign_type_id', $this->_values)); + // Assign custom data subtype for initial ajax load of custom data. + $this->assign('entityID', $this->getCampaignID()); + $this->assign('customDataSubType', $this->getSubmittedValue('campaign_type_id') ?: $this->getCampaignValue('campaign_type_id')); $attributes = CRM_Core_DAO::getAttribute('CRM_Campaign_DAO_Campaign'); - // add comaign title. $this->add('text', 'title', ts('Title'), $attributes['title'], TRUE); - - // add description $this->add('textarea', 'description', ts('Description'), $attributes['description']); - - // add campaign start date $this->add('datepicker', 'start_date', ts('Start Date'), [], TRUE); - - // add campaign end date $this->add('datepicker', 'end_date', ts('End Date')); - - // add campaign type $this->addSelect('campaign_type_id', ['placeholder' => ts('- select type -'), 'onChange' => "CRM.buildCustomData( 'Campaign', this.value );"], TRUE); - - // add campaign status $this->addSelect('status_id', ['placeholder' => ts('- select status -')]); // add External Identifier Element @@ -199,7 +209,7 @@ public function buildQuickForm() { ); // add Campaign Parent Id - $campaigns = CRM_Campaign_BAO_Campaign::getCampaigns(CRM_Utils_Array::value('parent_id', $this->_values), $this->_campaignId); + $campaigns = CRM_Campaign_BAO_Campaign::getCampaigns($this->getCampaignValue('parent_id'), $this->getCampaignID()); if (!empty($campaigns)) { $this->addElement('select', 'parent_id', ts('Parent ID'), ['' => ts('- select Parent -')] + $campaigns, @@ -245,6 +255,22 @@ public function buildQuickForm() { $this->addFormRule(['CRM_Campaign_Form_Campaign', 'formRule']); } + /** + * Get the selected Campaign ID. + * + * @api This function will not change in a minor release and is supported for + * use outside of core. This annotation / external support for properties + * is only given where there is specific test cover. + * + * @noinspection PhpUnhandledExceptionInspection + */ + public function getCampaignID(): ?int { + if (!isset($this->_campaignId)) { + $this->_campaignId = CRM_Utils_Request::retrieve('id', 'Positive') ?: NULL; + } + return $this->_campaignId; + } + /** * add the rules (mainly global rules) for form. * All local rules are added near the element @@ -268,11 +294,11 @@ public static function formRule($fields) { /** * Form submission of new/edit campaign is processed. */ - public function postProcess() { + public function postProcess(): void { // store the submitted values in an array $session = CRM_Core_Session::singleton(); - $params = $this->controller->exportValues($this->_name); + $params = $this->getSubmittedValues(); // To properly save the DAO we need to ensure we don't have a blank id key passed through. if (empty($params['id'])) { unset($params['id']); @@ -314,7 +340,7 @@ public function postProcess() { public static function submit($params, $form) { $groups = []; if (!empty($params['includeGroups']) && is_array($params['includeGroups'])) { - foreach ($params['includeGroups'] as $key => $id) { + foreach ($params['includeGroups'] as $id) { if ($id) { $groups['include'][] = $id; } @@ -340,8 +366,7 @@ public static function submit($params, $form) { // dev/core#1067 Clean Money before passing onto BAO to do the create. $params['goal_revenue'] = CRM_Utils_Rule::cleanMoney($params['goal_revenue']); - $result = civicrm_api3('Campaign', 'create', $params); - return $result; + return civicrm_api3('Campaign', 'create', $params); } } diff --git a/www/modules/civicrm/CRM/Campaign/Form/CampaignFormTrait.php b/www/modules/civicrm/CRM/Campaign/Form/CampaignFormTrait.php new file mode 100644 index 000000000..17c966b83 --- /dev/null +++ b/www/modules/civicrm/CRM/Campaign/Form/CampaignFormTrait.php @@ -0,0 +1,51 @@ +isDefined('Campaign')) { + return $this->lookup('Campaign', $fieldName); + } + $id = $this->getCampaignID(); + if ($id) { + $this->define('Campaign', 'Campaign', ['id' => $id]); + return $this->lookup('Campaign', $fieldName); + } + return NULL; + } + + /** + * Get the selected Campaign ID. + * + * @api This function will not change in a minor release and is supported for + * use outside of core. This annotation / external support for properties + * is only given where there is specific test cover. + * + * @noinspection PhpUnhandledExceptionInspection + */ + public function getCampaignID(): ?int { + throw new CRM_Core_Exception('`getCampaignID` must be implemented'); + } + +} diff --git a/www/modules/civicrm/CRM/Campaign/Form/Petition.php b/www/modules/civicrm/CRM/Campaign/Form/Petition.php index c12a653c1..202d91ad5 100644 --- a/www/modules/civicrm/CRM/Campaign/Form/Petition.php +++ b/www/modules/civicrm/CRM/Campaign/Form/Petition.php @@ -19,6 +19,7 @@ * This class generates form components for adding a petition. */ class CRM_Campaign_Form_Petition extends CRM_Core_Form { + use CRM_Custom_Form_CustomDataTrait; /** * Making this public so we can reference it in the formRule @@ -42,6 +43,24 @@ public function getEntityId() { return $this->_surveyId; } + /** + * Get the survey ID. + * + * @api supported for external use. + * + * @return int|null + * @throws \CRM_Core_Exception + */ + public function getSurveyID(): ?int { + if (!isset($this->_surveyId)) { + $this->_surveyId = CRM_Utils_Request::retrieve('id', 'Positive', $this); + } + return $this->_surveyId; + } + + /** + * @throws \CRM_Core_Exception + */ public function preProcess() { if (!CRM_Campaign_BAO_Campaign::accessCampaign()) { CRM_Utils_System::permissionDenied(); @@ -64,8 +83,15 @@ public function preProcess() { } } - // Add custom data to form - CRM_Custom_Form_CustomData::addToForm($this); + if ($this->isSubmitted()) { + // The custom data fields are added to the form by an ajax form. + // However, if they are not present in the element index they will + // not be available from `$this->getSubmittedValue()` in post process. + // We do not have to set defaults or otherwise render - just add to the element index. + $this->addCustomDataFieldsToForm('Survey', array_filter([ + 'id' => $this->getSurveyID(), + ])); + } $session = CRM_Core_Session::singleton(); $url = CRM_Utils_System::url('civicrm/campaign', 'reset=1&subPage=survey'); @@ -341,9 +367,7 @@ public function postProcess() { CRM_Core_BAO_UFJoin::create($ufJoinParams); } - if (!is_a($surveyId, 'CRM_Core_Error')) { - CRM_Core_Session::setStatus(ts('Petition has been saved.'), ts('Saved'), 'success'); - } + CRM_Core_Session::setStatus(ts('Petition has been saved.'), ts('Saved'), 'success'); $buttonName = $this->controller->getButtonName(); if ($buttonName == $this->getButtonName('next', 'new')) { diff --git a/www/modules/civicrm/CRM/Campaign/Form/Survey.php b/www/modules/civicrm/CRM/Campaign/Form/Survey.php index 74d1563da..e57f18591 100644 --- a/www/modules/civicrm/CRM/Campaign/Form/Survey.php +++ b/www/modules/civicrm/CRM/Campaign/Form/Survey.php @@ -19,11 +19,14 @@ * This class generates form components for processing a survey. */ class CRM_Campaign_Form_Survey extends CRM_Core_Form { + use CRM_Custom_Form_CustomDataTrait; /** * The id of the object being edited. * * @var int + * + * @internal */ protected $_surveyId; @@ -51,12 +54,17 @@ public function getDefaultEntity() { /** * Get the entity id being edited. * + * @internal + * * @return int|null */ public function getEntityId() { return $this->_surveyId; } + /** + * @throws \CRM_Core_Exception + */ public function preProcess() { // Multistep form doesn't play well with popups $this->preventAjaxSubmit(); @@ -66,9 +74,7 @@ public function preProcess() { } $this->_action = CRM_Utils_Request::retrieve('action', 'String', $this, FALSE, 'add', 'REQUEST'); - $this->_surveyId = CRM_Utils_Request::retrieve('id', 'Positive', $this, FALSE); - - if ($this->_surveyId) { + if ($this->getSurveyID()) { $this->_single = TRUE; $params = ['id' => $this->_surveyId]; @@ -79,10 +85,17 @@ public function preProcess() { } $this->assign('action', $this->_action); - $this->assign('surveyId', $this->_surveyId); + $this->assign('surveyId', $this->getSurveyID()); - // Add custom data to form - CRM_Custom_Form_CustomData::addToForm($this); + if ($this->isSubmitted()) { + // The custom data fields are added to the form by an ajax form. + // However, if they are not present in the element index they will + // not be available from `$this->getSubmittedValue()` in post process. + // We do not have to set defaults or otherwise render - just add to the element index. + $this->addCustomDataFieldsToForm('Survey', array_filter([ + 'id' => $this->getSurveyID(), + ])); + } // CRM-11480, CRM-11682 // Preload libraries required by the "Questions" tab @@ -90,13 +103,141 @@ public function preProcess() { CRM_UF_Page_ProfileEditor::registerProfileScripts(); CRM_UF_Page_ProfileEditor::registerSchemas(['IndividualModel', 'ActivityModel']); - CRM_Campaign_Form_Survey_TabHeader::build($this); + $this->build(); + } + + /** + * Get the survey ID. + * + * @api supported for external use. + * + * @return int|null + * @throws \CRM_Core_Exception + */ + public function getSurveyID(): ?int { + if (!isset($this->_surveyId)) { + $this->_surveyId = CRM_Utils_Request::retrieve('id', 'Positive', $this); + } + return $this->_surveyId; + } + + /** + * Build tab header. + * + * @return array + */ + private function build() { + $form = $this; + $tabs = $form->get('tabHeader'); + if (!$tabs || empty($_GET['reset'])) { + $tabs = $this->processSurveyForm(); + $form->set('tabHeader', $tabs); + } + $form->assign('tabHeader', $tabs); + CRM_Core_Resources::singleton() + ->addScriptFile('civicrm', 'templates/CRM/common/TabHeader.js', 1, 'html-header') + ->addSetting([ + 'tabSettings' => [ + 'active' => $this->getCurrentTab($tabs), + ], + ]); + return $tabs; + } + + /** + * @param array $tabs + * + * @return int|string + */ + private function getCurrentTab($tabs) { + static $current = FALSE; + + if ($current) { + return $current; + } + + if (is_array($tabs)) { + foreach ($tabs as $subPage => $pageVal) { + if ($pageVal['current'] === TRUE) { + $current = $subPage; + break; + } + } + } + + $current = $current ?: 'main'; + return $current; + } + + /** + * + * @return array + * @throws \CRM_Core_Exception + */ + private function processSurveyForm() { + $form = $this; + if ($this->getSurveyID() <= 0) { + return NULL; + } + + $tabs = [ + 'main' => [ + 'title' => ts('Main Information'), + 'link' => NULL, + 'valid' => FALSE, + 'active' => FALSE, + 'current' => FALSE, + ], + 'questions' => [ + 'title' => ts('Questions'), + 'link' => NULL, + 'valid' => FALSE, + 'active' => FALSE, + 'current' => FALSE, + ], + 'results' => [ + 'title' => ts('Results'), + 'link' => NULL, + 'valid' => FALSE, + 'active' => FALSE, + 'current' => FALSE, + ], + ]; + + $surveyID = $this->getSurveyID(); + $class = $this->_name; + $class = CRM_Utils_String::getClassName($class); + $class = strtolower($class); + + if (array_key_exists($class, $tabs)) { + $tabs[$class]['current'] = TRUE; + $qfKey = $form->get('qfKey'); + if ($qfKey) { + $tabs[$class]['qfKey'] = "&qfKey={$qfKey}"; + } + } + + if ($surveyID) { + $reset = !empty($_GET['reset']) ? 'reset=1&' : ''; + + foreach ($tabs as $key => $value) { + if (!isset($tabs[$key]['qfKey'])) { + $tabs[$key]['qfKey'] = NULL; + } + + $tabs[$key]['link'] = CRM_Utils_System::url("civicrm/survey/configure/{$key}", + "{$reset}action=update&id={$surveyID}{$tabs[$key]['qfKey']}" + ); + $tabs[$key]['active'] = $tabs[$key]['valid'] = TRUE; + } + } + return $tabs; } /** * Build the form object. */ - public function buildQuickForm() { + public function buildQuickForm(): void { $session = CRM_Core_Session::singleton(); if ($this->_surveyId) { $buttons = [ @@ -131,7 +272,7 @@ public function endPostProcess() { // make submit buttons keep the current working tab opened. if ($this->_action & (CRM_Core_Action::UPDATE | CRM_Core_Action::ADD)) { $tabTitle = $className = CRM_Utils_String::getClassName($this->_name); - if ($tabTitle == 'Main') { + if ($tabTitle === 'Main') { $tabTitle = 'Main settings'; } $subPage = strtolower($className); @@ -143,11 +284,11 @@ public function endPostProcess() { CRM_Utils_System::redirect(CRM_Utils_System::url("civicrm/survey/configure/questions", "action=update&reset=1&id={$this->_surveyId}")); } - if ($this->controller->getButtonName('submit') == "_qf_{$className}_upload_done") { + if ($this->controller->getButtonName('submit') === "_qf_{$className}_upload_done") { CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/campaign', 'reset=1&subPage=survey')); } - elseif ($this->controller->getButtonName('submit') == "_qf_{$className}_upload_next") { - $subPage = CRM_Campaign_Form_Survey_TabHeader::getNextTab($this); + elseif ($this->controller->getButtonName('submit') === "_qf_{$className}_upload_next") { + $subPage = $this->getNextTab(); CRM_Utils_System::redirect(CRM_Utils_System::url("civicrm/survey/configure/{$subPage}", "action=update&reset=1&id={$this->_surveyId}")); } @@ -170,4 +311,33 @@ public function getTemplateFileName(): string { return 'CRM/Campaign/Form/Survey/Tab.tpl'; } + /** + * + * @return int|string + */ + private function getNextTab() { + $form = $this; + static $next = FALSE; + if ($next) { + return $next; + } + + $tabs = $form->get('tabHeader'); + if (is_array($tabs)) { + $current = FALSE; + foreach ($tabs as $subPage => $pageVal) { + if ($current) { + $next = $subPage; + break; + } + if ($pageVal['current'] === TRUE) { + $current = $subPage; + } + } + } + + $next = $next ?: 'main'; + return $next; + } + } diff --git a/www/modules/civicrm/CRM/Campaign/Form/Survey/Main.php b/www/modules/civicrm/CRM/Campaign/Form/Survey/Main.php index 88aa3c9eb..20bf3f5a1 100644 --- a/www/modules/civicrm/CRM/Campaign/Form/Survey/Main.php +++ b/www/modules/civicrm/CRM/Campaign/Form/Survey/Main.php @@ -50,8 +50,15 @@ public function preProcess() { $this->setTitle(ts('Configure Survey') . ' - ' . $this->_surveyTitle); } - // Add custom data to form - CRM_Custom_Form_CustomData::addToForm($this); + if ($this->isSubmitted()) { + // The custom data fields are added to the form by an ajax form. + // However, if they are not present in the element index they will + // not be available from `$this->getSubmittedValue()` in post process. + // We do not have to set defaults or otherwise render - just add to the element index. + $this->addCustomDataFieldsToForm('Survey', array_filter([ + 'id' => $this->getSurveyID(), + ])); + } if ($this->_name != 'Petition') { $url = CRM_Utils_System::url('civicrm/campaign', 'reset=1&subPage=survey'); @@ -98,7 +105,7 @@ public function setDefaultValues() { /** * Build the form object. */ - public function buildQuickForm() { + public function buildQuickForm(): void { $this->add('text', 'title', ts('Title'), CRM_Core_DAO::getAttribute('CRM_Campaign_DAO_Survey', 'title'), TRUE); // Activity Type id @@ -158,7 +165,7 @@ public function postProcess() { $params['is_active'] ??= 0; $params['is_default'] ??= 0; - $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params, $this->getEntityId(), $this->getDefaultEntity()); + $params['custom'] = CRM_Core_BAO_CustomField::postProcess($this->getSubmittedValues(), $this->getSurveyID(), 'Survey'); $survey = CRM_Campaign_BAO_Survey::create($params); $this->_surveyId = $survey->id; diff --git a/www/modules/civicrm/CRM/Campaign/Form/Survey/Questions.php b/www/modules/civicrm/CRM/Campaign/Form/Survey/Questions.php index 3dae1e491..7ec61dc46 100644 --- a/www/modules/civicrm/CRM/Campaign/Form/Survey/Questions.php +++ b/www/modules/civicrm/CRM/Campaign/Form/Survey/Questions.php @@ -47,9 +47,9 @@ public function setDefaultValues() { /** * Build the form object. */ - public function buildQuickForm() { + public function buildQuickForm(): void { $subTypeId = CRM_Core_DAO::getFieldValue('CRM_Campaign_DAO_Survey', $this->_surveyId, 'activity_type_id'); - if (!CRM_Core_BAO_CustomGroup::autoCreateByActivityType($subTypeId)) { + if (!self::autoCreateCustomGroup($subTypeId)) { // everything $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'label', TRUE, FALSE); // FIXME: Displays weird "/\ Array" message; doesn't work with tabs @@ -80,6 +80,26 @@ public function buildQuickForm() { parent::buildQuickForm(); } + public static function autoCreateCustomGroup($activityTypeId) { + $existing = CRM_Core_BAO_CustomGroup::getAll(['extends' => 'Activity', 'extends_entity_column_value' => $activityTypeId]); + if ($existing) { + return TRUE; + } + // everything + $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'label', TRUE, FALSE); + $params = [ + 'version' => 3, + 'extends' => 'Activity', + 'extends_entity_column_id' => NULL, + 'extends_entity_column_value' => CRM_Utils_Array::implodePadded([$activityTypeId]), + 'title' => ts('%1 Questions', [1 => $activityTypes[$activityTypeId]]), + 'style' => 'Inline', + 'is_active' => 1, + ]; + $result = civicrm_api('CustomGroup', 'create', $params); + return !$result['is_error']; + } + /** * Process the form. */ diff --git a/www/modules/civicrm/CRM/Campaign/Form/Survey/Results.php b/www/modules/civicrm/CRM/Campaign/Form/Survey/Results.php index 27121645e..fdc2e54df 100644 --- a/www/modules/civicrm/CRM/Campaign/Form/Survey/Results.php +++ b/www/modules/civicrm/CRM/Campaign/Form/Survey/Results.php @@ -83,7 +83,7 @@ public function setDefaultValues() { /** * Build the form object. */ - public function buildQuickForm() { + public function buildQuickForm(): void { $optionGroups = CRM_Campaign_BAO_Survey::getResultSets(); if (empty($optionGroups)) { diff --git a/www/modules/civicrm/CRM/Campaign/Form/Survey/TabHeader.php b/www/modules/civicrm/CRM/Campaign/Form/Survey/TabHeader.php index 2856c09a3..c8b7ceae1 100644 --- a/www/modules/civicrm/CRM/Campaign/Form/Survey/TabHeader.php +++ b/www/modules/civicrm/CRM/Campaign/Form/Survey/TabHeader.php @@ -16,24 +16,27 @@ */ /** - * Helper class to build navigation links + * @deprecated since 5.71 will be removed around 5.77 */ class CRM_Campaign_Form_Survey_TabHeader { /** * Build tab header. * + * @deprecated since 5.71 will be removed around 5.77 + * * @param CRM_Core_Form $form * * @return array */ public static function build(&$form) { + CRM_Core_Error::deprecatedFunctionWarning('no alternative'); $tabs = $form->get('tabHeader'); if (!$tabs || empty($_GET['reset'])) { $tabs = self::process($form); $form->set('tabHeader', $tabs); } - $form->assign_by_ref('tabHeader', $tabs); + $form->assign('tabHeader', $tabs); CRM_Core_Resources::singleton() ->addScriptFile('civicrm', 'templates/CRM/common/TabHeader.js', 1, 'html-header') ->addSetting([ @@ -48,8 +51,11 @@ public static function build(&$form) { * @param CRM_Core_Form $form * * @return array + * + * @deprecated since 5.71 will be removed around 5.77 */ public static function process(&$form) { + CRM_Core_Error::deprecatedFunctionWarning('no alternative'); if ($form->getVar('_surveyId') <= 0) { return NULL; } @@ -110,8 +116,11 @@ public static function process(&$form) { /** * @param CRM_Core_Form $form + * + * @deprecated since 5.71 will be removed around 5.77 */ public static function reset(&$form) { + CRM_Core_Error::deprecatedFunctionWarning('no alternative'); $tabs = self::process($form); $form->set('tabHeader', $tabs); } @@ -120,8 +129,11 @@ public static function reset(&$form) { * @param array $tabs * * @return int|string + * + * @deprecated since 5.71 will be removed around 5.77 */ public static function getCurrentTab($tabs) { + CRM_Core_Error::deprecatedFunctionWarning('no alternative'); static $current = FALSE; if ($current) { @@ -145,8 +157,11 @@ public static function getCurrentTab($tabs) { * @param CRM_Core_Form $form * * @return int|string + * + * @deprecated since 5.71 will be removed around 5.77 */ public static function getNextTab(&$form) { + CRM_Core_Error::deprecatedFunctionWarning('no alternative'); static $next = FALSE; if ($next) { return $next; diff --git a/www/modules/civicrm/CRM/Campaign/Info.php b/www/modules/civicrm/CRM/Campaign/Info.php index 6727a595d..f1252e5df 100644 --- a/www/modules/civicrm/CRM/Campaign/Info.php +++ b/www/modules/civicrm/CRM/Campaign/Info.php @@ -41,49 +41,37 @@ public function getInfo() { /** * @inheritDoc - * @param bool $getAllUnconditionally - * @param bool $descriptions - * Whether to return permission descriptions - * - * @return array */ - public function getPermissions($getAllUnconditionally = FALSE, $descriptions = FALSE) { + public function getPermissions(): array { $permissions = [ 'administer CiviCampaign' => [ - ts('administer CiviCampaign'), - ts('Create new campaign, survey and petition types and their status'), + 'label' => ts('administer CiviCampaign'), + 'description' => ts('Create new campaign, survey and petition types and their status'), ], 'manage campaign' => [ - ts('manage campaign'), - ts('Create new campaigns, surveys and petitions, reserve respondents'), + 'label' => ts('manage campaign'), + 'description' => ts('Create new campaigns, surveys and petitions, reserve respondents'), ], 'reserve campaign contacts' => [ - ts('reserve campaign contacts'), - ts('Reserve campaign contacts for surveys and petitions'), + 'label' => ts('reserve campaign contacts'), + 'description' => ts('Reserve campaign contacts for surveys and petitions'), ], 'release campaign contacts' => [ - ts('release campaign contacts'), - ts('Release reserved campaign contacts for surveys and petitions'), + 'label' => ts('release campaign contacts'), + 'description' => ts('Release reserved campaign contacts for surveys and petitions'), ], 'interview campaign contacts' => [ - ts('interview campaign contacts'), - ts('Record survey and petition responses from their reserved contacts'), + 'label' => ts('interview campaign contacts'), + 'description' => ts('Record survey and petition responses from their reserved contacts'), ], 'gotv campaign contacts' => [ - ts('GOTV campaign contacts'), - ts('Record that contacts voted'), + 'label' => ts('GOTV campaign contacts'), + 'description' => ts('Record that contacts voted'), ], 'sign CiviCRM Petition' => [ - ts('sign CiviCRM Petition'), + 'label' => ts('sign CiviCRM Petition'), ], ]; - - if (!$descriptions) { - foreach ($permissions as $name => $attr) { - $permissions[$name] = array_shift($attr); - } - } - return $permissions; } diff --git a/www/modules/civicrm/CRM/Campaign/Page/AJAX.php b/www/modules/civicrm/CRM/Campaign/Page/AJAX.php index 7d99c2f66..8643c4d76 100644 --- a/www/modules/civicrm/CRM/Campaign/Page/AJAX.php +++ b/www/modules/civicrm/CRM/Campaign/Page/AJAX.php @@ -290,7 +290,7 @@ public static function voterList() { ); while ($result->fetch()) { $contactID = $result->contact_id; - $typeImage = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ? $result->contact_sub_type : $result->contact_type, + $typeImage = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ?: $result->contact_type, FALSE, $result->contact_id ); diff --git a/www/modules/civicrm/CRM/Campaign/Page/Petition/ThankYou.php b/www/modules/civicrm/CRM/Campaign/Page/Petition/ThankYou.php index d2bf89eca..6c0ede29d 100644 --- a/www/modules/civicrm/CRM/Campaign/Page/Petition/ThankYou.php +++ b/www/modules/civicrm/CRM/Campaign/Page/Petition/ThankYou.php @@ -28,11 +28,11 @@ public function run() { $petition = []; CRM_Campaign_BAO_Survey::retrieve($params, $petition); $this->assign('petitionTitle', $petition['title']); - $this->assign('thankyou_title', CRM_Utils_Array::value('thankyou_title', $petition)); - $this->assign('thankyou_text', CRM_Utils_Array::value('thankyou_text', $petition)); + $this->assign('thankyou_title', $petition['thankyou_title'] ?? NULL); + $this->assign('thankyou_text', $petition['thankyou_text'] ?? NULL); $this->assign('survey_id', $petition_id); $this->assign('status_id', $id); - $this->assign('is_share', CRM_Utils_Array::value('is_share', $petition)); + $this->assign('is_share', $petition['is_share'] ?? NULL); CRM_Utils_System::setTitle(CRM_Utils_Array::value('thankyou_title', $petition)); // send thank you or email verification emails diff --git a/www/modules/civicrm/CRM/Campaign/Selector/Search.php b/www/modules/civicrm/CRM/Campaign/Selector/Search.php index 097178b9b..a7d6a3aa9 100644 --- a/www/modules/civicrm/CRM/Campaign/Selector/Search.php +++ b/www/modules/civicrm/CRM/Campaign/Selector/Search.php @@ -146,10 +146,31 @@ public function __construct( // type of selector $this->_action = $action; - $this->_query = new CRM_Contact_BAO_Query($this->_queryParams, - NULL, NULL, FALSE, FALSE, - CRM_Contact_BAO_Query::MODE_CAMPAIGN, - TRUE + $params = $this->_queryParams; + $returnProperties = NULL; + $fields = NULL; + $includeContactIds = FALSE; + $strict = FALSE; + $mode = CRM_Contact_BAO_Query::MODE_CAMPAIGN; + $skipPermission = TRUE; + $searchDescendentGroups = TRUE; + $smartGroupCache = TRUE; + $displayRelationshipType = NULL; + $operator = 'AND'; + $apiEntity = NULL; + // This is flipped from the default of NULL. When primaryLocationOnly is NULL + // it will be based on the value of the 'searchPrimaryDetailsOnly' setting, which is often + // set to FALSE so you can search for non-primary location fields in advanced search. But, + // when reserving people for a survey, we only want each person listed once, not once for + // every combination of location types they have for email, phone, and address. + $primaryLocationOnly = TRUE; + + $this->_query = new CRM_Contact_BAO_Query( + $params, $returnProperties, $fields, + $includeContactIds, $strict, $mode, + $skipPermission, $searchDescendentGroups, + $smartGroupCache, $displayRelationshipType, + $operator, $apiEntity, $primaryLocationOnly ); } diff --git a/www/modules/civicrm/CRM/Case/Audit/Audit.php b/www/modules/civicrm/CRM/Case/Audit/Audit.php index 9f9128241..187430900 100644 --- a/www/modules/civicrm/CRM/Case/Audit/Audit.php +++ b/www/modules/civicrm/CRM/Case/Audit/Audit.php @@ -225,7 +225,7 @@ public static function run($xmlString, $clientID, $caseID) { $activities = $audit->getActivities(TRUE); $template = CRM_Core_Smarty::singleton(); - $template->assign_by_ref('activities', $activities); + $template->assign('activities', $activities); $reportDate = CRM_Utils_Date::customFormat(date('Y-m-d H:i')); $template->assign('reportDate', $reportDate); diff --git a/www/modules/civicrm/CRM/Case/BAO/Case.php b/www/modules/civicrm/CRM/Case/BAO/Case.php index f58198ba4..2c6952aad 100644 --- a/www/modules/civicrm/CRM/Case/BAO/Case.php +++ b/www/modules/civicrm/CRM/Case/BAO/Case.php @@ -1405,7 +1405,6 @@ public static function sendActivityCopy($clientId, $activityId, $contacts, $atta [$result[$info['contact_id'] ?? NULL], $subject, $message, $html] = CRM_Core_BAO_MessageTemplate::sendTemplate( [ - 'groupName' => 'msg_tpl_workflow_case', 'workflow' => 'case_activity', 'contactId' => $info['contact_id'] ?? NULL, 'tplParams' => $tplParams, @@ -1413,6 +1412,12 @@ public static function sendActivityCopy($clientId, $activityId, $contacts, $atta 'toName' => $displayName, 'toEmail' => $mail, 'attachments' => $attachments, + 'modelProps' => $caseId ? [ + 'activityID' => $activityId, + 'caseID' => $caseId, + ] : [ + 'activityID' => $activityId, + ], ] ); @@ -2990,11 +2995,28 @@ public static function buildOptions($fieldName, $context = NULL, $props = []) { // Filter status id by case type id case 'status_id': - if (!empty($props['case_type_id']) && is_scalar($props['case_type_id'])) { - $idField = is_numeric($props['case_type_id']) ? 'id' : 'name'; - $caseType = civicrm_api3('CaseType', 'getsingle', [$idField => $props['case_type_id'], 'return' => 'definition']); - if (!empty($caseType['definition']['statuses'])) { - $params['condition'] = 'v.name IN ("' . implode('","', $caseType['definition']['statuses']) . '")'; + if (!empty($props['case_type_id'])) { + // cast single values to a single value array + $caseTypeIdValues = (array) $props['case_type_id']; + + $idField = is_numeric($caseTypeIdValues[0]) ? 'id' : 'name'; + $caseTypeDefs = (array) \Civi\Api4\CaseType::get(FALSE) + ->addSelect('definition') + ->addWhere($idField, 'IN', $caseTypeIdValues) + ->execute()->column('definition'); + + $allowAll = FALSE; + $statuses = []; + foreach ($caseTypeDefs as $definition) { + if (empty($definition['statuses'])) { + // if any case type has no status restrictions, we want to allow all options + $allowAll = TRUE; + break; + } + $statuses = array_unique(array_merge($statuses, $definition['statuses'])); + } + if (!$allowAll) { + $params['condition'] = 'v.name IN ("' . implode('","', $statuses) . '")'; } } break; diff --git a/www/modules/civicrm/CRM/Case/DAO/Case.php b/www/modules/civicrm/CRM/Case/DAO/Case.php index ad30a3f24..9eaf5e838 100644 --- a/www/modules/civicrm/CRM/Case/DAO/Case.php +++ b/www/modules/civicrm/CRM/Case/DAO/Case.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Case/Case.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:ae2ebb6d7b065ab0e098e67f581cc874) + * (GenCodeChecksum:d8669ec8aecbc1ba74f28e993a0013e5) */ /** diff --git a/www/modules/civicrm/CRM/Case/DAO/CaseActivity.php b/www/modules/civicrm/CRM/Case/DAO/CaseActivity.php index 422de4f51..085cb3dc1 100644 --- a/www/modules/civicrm/CRM/Case/DAO/CaseActivity.php +++ b/www/modules/civicrm/CRM/Case/DAO/CaseActivity.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Case/CaseActivity.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:0800682b3ed520cf7a9295226219cef2) + * (GenCodeChecksum:cbe351d41cb72378e89bd5cbab7210d7) */ /** diff --git a/www/modules/civicrm/CRM/Case/DAO/CaseContact.php b/www/modules/civicrm/CRM/Case/DAO/CaseContact.php index 44e776cf2..fcfa30ea7 100644 --- a/www/modules/civicrm/CRM/Case/DAO/CaseContact.php +++ b/www/modules/civicrm/CRM/Case/DAO/CaseContact.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Case/CaseContact.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:e86592582af3784c253cac8744460ffe) + * (GenCodeChecksum:83e20edc6a43864c63bc9fa6a2068f69) */ /** diff --git a/www/modules/civicrm/CRM/Case/DAO/CaseType.php b/www/modules/civicrm/CRM/Case/DAO/CaseType.php index 19676df01..febca8aa0 100644 --- a/www/modules/civicrm/CRM/Case/DAO/CaseType.php +++ b/www/modules/civicrm/CRM/Case/DAO/CaseType.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Case/CaseType.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:942b11d00fab3506535e81984187a767) + * (GenCodeChecksum:93766f240762bc60eec5a44f549a34ce) */ /** diff --git a/www/modules/civicrm/CRM/Case/Form/Activity.php b/www/modules/civicrm/CRM/Case/Form/Activity.php index 6a8287090..98be72cdb 100644 --- a/www/modules/civicrm/CRM/Case/Form/Activity.php +++ b/www/modules/civicrm/CRM/Case/Form/Activity.php @@ -49,6 +49,32 @@ class CRM_Case_Form_Activity extends CRM_Activity_Form_Activity { */ public $_caseTypeDefinition; + /** + * This is here to avoid php 8 warnings but it should be converted to + * some mechanism more local to ChangeCaseStatus. It also doesn't make sense + * that it's an array. + * + * @var array + * @internal + */ + public $_oldCaseStatus; + + /** + * This is here to avoid php 8 warnings but it should be converted to + * some mechanism more local to ChangeCaseStatus. It also doesn't make sense + * that it's an array. + * + * @var array + * @internal + */ + public $_defaultCaseStatus; + + /** + * @var int + * Used by ChangeCaseStartDate. See getter/setter below. + */ + private $openCaseActivityId; + /** * Build the form object. */ @@ -401,35 +427,33 @@ public function postProcess($params = NULL) { } // format activity custom data - if (!empty($params['hidden_custom'])) { - if ($this->_activityId) { - // retrieve and include the custom data of old Activity - $oldActivity = civicrm_api3('Activity', 'getsingle', ['id' => $this->_activityId]); - $params = array_merge($oldActivity, $params); - - // unset custom fields-id from params since we want custom - // fields to be saved for new activity. - foreach ($params as $key => $value) { - $match = []; - if (preg_match('/^(custom_\d+_)(\d+)$/', $key, $match)) { - $params[$match[1] . '-1'] = $params[$key]; - - // for autocomplete transfer hidden value instead of label - if ($params[$key] && isset($params[$key . '_id'])) { - $params[$match[1] . '-1_id'] = $params[$key . '_id']; - unset($params[$key . '_id']); - } - unset($params[$key]); + if ($this->_activityId) { + // retrieve and include the custom data of old Activity + $oldActivity = civicrm_api3('Activity', 'getsingle', ['id' => $this->_activityId]); + $params = array_merge($oldActivity, $params); + + // unset custom fields-id from params since we want custom + // fields to be saved for new activity. + foreach ($params as $key => $value) { + $match = []; + if (preg_match('/^(custom_\d+_)(\d+)$/', $key, $match)) { + $params[$match[1] . '-1'] = $params[$key]; + + // for autocomplete transfer hidden value instead of label + if ($params[$key] && isset($params[$key . '_id'])) { + $params[$match[1] . '-1_id'] = $params[$key . '_id']; + unset($params[$key . '_id']); } + unset($params[$key]); } } - - $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params, - $this->_activityId, - 'Activity' - ); } + $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params, + $this->_activityId, + 'Activity' + ); + // assigning formatted value if (!empty($params['assignee_contact_id'])) { $params['assignee_contact_id'] = explode(',', $params['assignee_contact_id']); @@ -445,7 +469,7 @@ public function postProcess($params = NULL) { } // @todo This is called newActParams because it USED TO create new activity revisions. But at the moment just changing the part that is broken. - // hidden_custom is always 1, so see above where $params gets merged with the existing activity data every time, including the activity id. + // $params gets merged with the existing activity data every time, including the activity id. $newActParams = $params; // add target contact values in update mode @@ -798,4 +822,22 @@ public static function getMaxInstancesBounceMessage($editUrl, $activityTypeName, return $bounceMessage; } + /** + * Getter used by ChangeCaseStartDate + * @return int|null + * @internal + */ + public function getOpenCaseActivityId(): ?int { + return $this->openCaseActivityId; + } + + /** + * Setter used by ChangeCaseStartDate + * @param int $id + * @internal + */ + public function setOpenCaseActivityId(int $id): void { + $this->openCaseActivityId = $id; + } + } diff --git a/www/modules/civicrm/CRM/Case/Form/Activity/ChangeCaseStartDate.php b/www/modules/civicrm/CRM/Case/Form/Activity/ChangeCaseStartDate.php index 1a62ae2c9..399a7b663 100644 --- a/www/modules/civicrm/CRM/Case/Form/Activity/ChangeCaseStartDate.php +++ b/www/modules/civicrm/CRM/Case/Form/Activity/ChangeCaseStartDate.php @@ -58,7 +58,7 @@ public static function setDefaultValues(&$form) { $openCaseInfo = current($openCaseInfo); // store activity id for updating it later - $form->openCaseActivityId = $openCaseInfo['id']; + $form->setOpenCaseActivityId($openCaseInfo['id']); $defaults['start_date'] = $openCaseInfo['activity_date']; } @@ -164,10 +164,10 @@ public static function endPostProcess(&$form, &$params, $activity) { // 2.5 Update open case activity date // @todo Since revisioning code has been removed this can be refactored more - if ($form->openCaseActivityId) { + if ($form->getOpenCaseActivityId()) { $abao = new CRM_Activity_BAO_Activity(); - $oldParams = ['id' => $form->openCaseActivityId]; + $oldParams = ['id' => $form->getOpenCaseActivityId()]; $oldActivityDefaults = []; $oldActivity = $abao->retrieve($oldParams, $oldActivityDefaults); diff --git a/www/modules/civicrm/CRM/Case/Form/Activity/ChangeCaseStatus.php b/www/modules/civicrm/CRM/Case/Form/Activity/ChangeCaseStatus.php index 77a30468f..fad64e52f 100644 --- a/www/modules/civicrm/CRM/Case/Form/Activity/ChangeCaseStatus.php +++ b/www/modules/civicrm/CRM/Case/Form/Activity/ChangeCaseStatus.php @@ -50,6 +50,7 @@ public static function preProcess(&$form) { public static function setDefaultValues(&$form) { $defaults = []; // Retrieve current case status + // @todo this var is created as an array below, but the form field is single-valued. See also comment about _oldCaseStatus in endPostProcess. $defaults['case_status_id'] = $form->_defaultCaseStatus; return $defaults; @@ -58,13 +59,13 @@ public static function setDefaultValues(&$form) { /** * @param CRM_Core_Form $form */ - public static function buildQuickForm(&$form) { + public static function buildQuickForm($form) { $form->removeElement('status_id'); $form->removeElement('priority_id'); $caseTypes = []; - $form->_caseStatus = CRM_Case_PseudoConstant::caseStatus(); + $statusLabels = CRM_Case_PseudoConstant::caseStatus(); $statusNames = CRM_Case_PseudoConstant::caseStatus('name'); // Limit case statuses to allowed types for these case(s) @@ -75,9 +76,9 @@ public static function buildQuickForm(&$form) { $caseTypes = civicrm_api3('CaseType', 'get', ['id' => ['IN' => $caseTypes]]); foreach ($caseTypes['values'] as $ct) { if (!empty($ct['definition']['statuses'])) { - foreach ($form->_caseStatus as $id => $label) { + foreach ($statusLabels as $id => $label) { if (!in_array($statusNames[$id], $ct['definition']['statuses'])) { - unset($form->_caseStatus[$id]); + unset($statusLabels[$id]); } } } @@ -88,12 +89,12 @@ public static function buildQuickForm(&$form) { } foreach ($form->_defaultCaseStatus as $keydefault => $valdefault) { - if (!array_key_exists($valdefault, $form->_caseStatus)) { - $form->_caseStatus[$valdefault] = CRM_Core_PseudoConstant::getLabel('CRM_Case_BAO_Case', 'status_id', $valdefault); + if (!array_key_exists($valdefault, $statusLabels)) { + $statusLabels[$valdefault] = CRM_Core_PseudoConstant::getLabel('CRM_Case_BAO_Case', 'status_id', $valdefault); } } $element = $form->add('select', 'case_status_id', ts('Case Status'), - $form->_caseStatus, TRUE + $statusLabels, TRUE ); // check if the case status id passed in url is a valid one, set as default and freeze if (CRM_Utils_Request::retrieve('case_status_id', 'Positive', $form)) { @@ -191,11 +192,15 @@ public static function endPostProcess(&$form, &$params, $activity) { $params['priority_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'priority_id', 'Normal'); $activity->priority_id = $params['priority_id']; + // Note here we don't need to do the filtering that happens in buildForm. + // All we want is the label for a given id so we can put it in the subject. + $statusLabels = CRM_Case_PseudoConstant::caseStatus(); foreach ($form->_oldCaseStatus as $statusval) { + // @todo we store all old statuses but then we only use the first one. if ($activity->subject == 'null') { $activity->subject = ts('Case status changed from %1 to %2', [ - 1 => $form->_caseStatus[$statusval] ?? NULL, - 2 => $form->_caseStatus[$params['case_status_id']] ?? NULL, + 1 => $statusLabels[$statusval] ?? NULL, + 2 => $statusLabels[$params['case_status_id']] ?? NULL, ]); $activity->save(); } diff --git a/www/modules/civicrm/CRM/Case/Form/Activity/OpenCase.php b/www/modules/civicrm/CRM/Case/Form/Activity/OpenCase.php index 6f01cce1b..db4692411 100644 --- a/www/modules/civicrm/CRM/Case/Form/Activity/OpenCase.php +++ b/www/modules/civicrm/CRM/Case/Form/Activity/OpenCase.php @@ -31,8 +31,8 @@ class CRM_Case_Form_Activity_OpenCase { * * @throws \CRM_Core_Exception */ - public static function preProcess(&$form) { - if ($form->_context == 'caseActivity') { + public static function preProcess(&$form): void { + if ($form->_context === 'caseActivity') { $contactID = CRM_Utils_Request::retrieve('cid', 'Positive', $form); $atype = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Change Case Start Date'); $caseId = CRM_Utils_Array::first($form->_caseId); @@ -74,7 +74,7 @@ public static function preProcess(&$form) { */ public static function setDefaultValues(&$form) { $defaults = []; - if ($form->_context == 'caseActivity') { + if ($form->_context === 'caseActivity') { return $defaults; } @@ -249,8 +249,8 @@ public static function formRule($fields, $files, $form) { * * @throws \Exception */ - public static function endPostProcess(&$form, &$params) { - if ($form->_context == 'caseActivity') { + public static function endPostProcess($form, &$params): void { + if ($form->_context === 'caseActivity') { return; } diff --git a/www/modules/civicrm/CRM/Case/Form/Case.php b/www/modules/civicrm/CRM/Case/Form/Case.php index da3c6cda7..130b0b422 100644 --- a/www/modules/civicrm/CRM/Case/Form/Case.php +++ b/www/modules/civicrm/CRM/Case/Form/Case.php @@ -19,6 +19,8 @@ * This class generates form components for case activity. */ class CRM_Case_Form_Case extends CRM_Core_Form { + use CRM_Custom_Form_CustomDataTrait; + use CRM_Case_Form_CaseFormTrait; /** * The context @@ -29,7 +31,12 @@ class CRM_Case_Form_Case extends CRM_Core_Form { /** * Case Id + * * @var int + * + * @internal + * + * use getCaseID to access. */ public $_caseId = NULL; @@ -77,6 +84,21 @@ class CRM_Case_Form_Case extends CRM_Core_Form { public $submitOnce = TRUE; + /** + * @var float|int|mixed|string|null + * + * This is inconsistently set & likely to be replaced by a local variable or getter. + */ + public $_contactID; + + /** + * @var float|int|mixed|string|null + * @deprecated + * + * This is inconsistently set & likely to be replaced by a local variable or getter. + */ + public $_caseStatusId; + /** * Explicitly declare the entity api name. */ @@ -95,6 +117,8 @@ public function getEntityId() { /** * Build the form object. + * + * @throws \CRM_Core_Exception */ public function preProcess() { if (empty($this->_action)) { @@ -177,22 +201,42 @@ public function preProcess() { } $this->assign('clientName', isset($this->_currentlyViewedContactId) ? $contact->display_name : NULL); - $session = CRM_Core_Session::singleton(); - $this->_currentUserId = $session->get('userID'); - - //Add activity custom data is included in this page - CRM_Custom_Form_CustomData::preProcess($this, NULL, $this->_activityTypeId, 1, 'Activity'); - $className = "CRM_Case_Form_Activity_{$this->_activityTypeFile}"; - $className::preProcess($this); - $activityGroupTree = $this->_groupTree; - - // Add case custom data to form - $caseTypeId = CRM_Utils_Array::value('case_type_id', CRM_Utils_Request::exportValues(), $this->_caseTypeId); - CRM_Custom_Form_CustomData::addToForm($this, $caseTypeId); + $this->_currentUserId = CRM_Core_Session::getLoggedInContactID(); + + CRM_Case_Form_Activity_OpenCase::preProcess($this); + + if ($this->isSubmitted()) { + // The custom data fields are added to the form by an ajax form. + // However, if they are not present in the element index they will + // not be available from `$this->getSubmittedValue()` in post process. + // We do not have to set defaults or otherwise render - just add to the element index. + $this->addCustomDataFieldsToForm('Case', array_filter([ + 'id' => $this->getCaseID(), + 'case_type_id' => $this->getSubmittedValue('case_type_id'), + ])); + $this->addCustomDataFieldsToForm('Activity', [ + 'activity_type_id' => $this->_activityTypeId, + ]); + } + // Used for loading custom data fields + $this->assign('activityTypeID', $this->_activityTypeId); + $this->assign('caseTypeID', $this->getSubmittedValue('case_type_id') ?: $this->getCaseValue('case_type_id')); + } - // so that grouptree is not populated with case fields, since the grouptree is used - // for populating activity custom fields. - $this->_groupTree = $activityGroupTree; + /** + * Get the selected Case ID. + * + * @api This function will not change in a minor release and is supported for + * use outside of core. This annotation / external support for properties + * is only given where there is specific test cover. + * + * @noinspection PhpUnhandledExceptionInspection + */ + public function getCaseID(): ?int { + if (!isset($this->_caseId)) { + $this->_caseId = CRM_Utils_Request::retrieve('id', 'Positive', $this); + } + return $this->_caseId; } /** @@ -202,10 +246,7 @@ public function setDefaultValues(): array { if ($this->_action & CRM_Core_Action::DELETE || $this->_action & CRM_Core_Action::RENEW) { return []; } - $className = "CRM_Case_Form_Activity_{$this->_activityTypeFile}"; - $defaults = $className::setDefaultValues($this); - $defaults = array_merge($defaults, CRM_Custom_Form_CustomData::setDefaultValues($this)); - return $defaults; + return CRM_Case_Form_Activity_OpenCase::setDefaultValues($this); } public function buildQuickForm() { @@ -233,9 +274,6 @@ public function buildQuickForm() { return; } - // Add the activity custom data to the form - CRM_Custom_Form_CustomData::buildQuickForm($this); - // we don't want to show button on top of custom form $this->assign('noPreCustomButton', TRUE); @@ -274,8 +312,7 @@ public function buildQuickForm() { ], ]); - $className = "CRM_Case_Form_Activity_{$this->_activityTypeFile}"; - $className::buildQuickForm($this); + CRM_Case_Form_Activity_OpenCase::buildQuickForm($this); } /** @@ -287,7 +324,7 @@ public function addRules() { if ($this->_action & CRM_Core_Action::DELETE || $this->_action & CRM_Core_Action::RENEW) { return TRUE; } - $className = "CRM_Case_Form_Activity_{$this->_activityTypeFile}"; + $className = "CRM_Case_Form_Activity_OpenCase"; $this->addFormRule([$className, 'formRule'], $this); $this->addFormRule(['CRM_Case_Form_Case', 'formRule'], $this); } @@ -308,25 +345,6 @@ public static function formRule($values, $files, $form) { return TRUE; } - /** - * Wrapper for unit testing the post process submit function. - * - * @param $params - * @param $activityTypeFile - * @param $contactId - * @param $context - * @return CRM_Case_BAO_Case - */ - public function testSubmit($params, $activityTypeFile, $contactId, $context = "case") { - $this->controller = new CRM_Core_Controller(); - - $this->_activityTypeFile = $activityTypeFile; - $this->_currentUserId = $contactId; - $this->_context = $context; - - return $this->submit($params); - } - /** * Submit the form with given params. * @@ -337,19 +355,14 @@ public function submit(&$params) { // 1. call begin post process if ($this->_activityTypeFile) { - $className = "CRM_Case_Form_Activity_{$this->_activityTypeFile}"; - $className::beginPostProcess($this, $params); + CRM_Case_Form_Activity_OpenCase::beginPostProcess($this, $params); } - if (!empty($params['hidden_custom']) && - !isset($params['custom']) - ) { - $params['custom'] = CRM_Core_BAO_CustomField::postProcess( - $params, - NULL, - 'Case' - ); - } + $params['custom'] = CRM_Core_BAO_CustomField::postProcess( + $this->getSubmittedValues(), + NULL, + 'Case' + ); // 2. create/edit case if (!empty($params['case_type_id'])) { @@ -388,16 +401,14 @@ public function submit(&$params) { CRM_Core_Session::singleton()->pushUserContext($url); // 3. format activity custom data - if (!empty($params['hidden_custom'])) { - $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params, - $this->_activityId, - 'Activity' - ); - } + $params['custom'] = CRM_Core_BAO_CustomField::postProcess($this->getSubmittedValues(), + $this->_activityId, + 'Activity' + ); // 4. call end post process if ($this->_activityTypeFile) { - $className::endPostProcess($this, $params); + CRM_Case_Form_Activity_OpenCase::endPostProcess($this, $params); } return $caseObj; diff --git a/www/modules/civicrm/CRM/Case/Form/CaseFormTrait.php b/www/modules/civicrm/CRM/Case/Form/CaseFormTrait.php new file mode 100644 index 000000000..960f878a5 --- /dev/null +++ b/www/modules/civicrm/CRM/Case/Form/CaseFormTrait.php @@ -0,0 +1,51 @@ +isDefined('Case')) { + return $this->lookup('Case', $fieldName); + } + $id = $this->getCaseID(); + if ($id) { + $this->define('Case', 'Case', ['id' => $id]); + return $this->lookup('Case', $fieldName); + } + return NULL; + } + + /** + * Get the selected Case ID. + * + * @api This function will not change in a minor release and is supported for + * use outside of core. This annotation / external support for properties + * is only given where there is specific test cover. + * + * @noinspection PhpUnhandledExceptionInspection + */ + public function getCaseID(): ?int { + throw new CRM_Core_Exception('`getCaseID` must be implemented'); + } + +} diff --git a/www/modules/civicrm/CRM/Case/Form/CaseView.php b/www/modules/civicrm/CRM/Case/Form/CaseView.php index 6ef33b6d8..c7a62ffd2 100644 --- a/www/modules/civicrm/CRM/Case/Form/CaseView.php +++ b/www/modules/civicrm/CRM/Case/Form/CaseView.php @@ -239,6 +239,8 @@ public function buildQuickForm() { return; } + $this->assign('hasAllACLs', CRM_Core_Permission::giveMeAllACLs()); + $allowedRelationshipTypes = CRM_Contact_BAO_Relationship::getContactRelationshipType($this->_contactID); $relationshipTypeMetadata = CRM_Contact_Form_Relationship::getRelationshipTypeMetadata($allowedRelationshipTypes); diff --git a/www/modules/civicrm/CRM/Case/Form/CustomData.php b/www/modules/civicrm/CRM/Case/Form/CustomData.php index 47dbd939b..b0eb6f26b 100644 --- a/www/modules/civicrm/CRM/Case/Form/CustomData.php +++ b/www/modules/civicrm/CRM/Case/Form/CustomData.php @@ -159,25 +159,26 @@ public function postProcess(): void { */ public function formatCustomDataChangesForDetail(array $params): string { $formattedDetails = []; - foreach ($params as $customField => $newCustomValue) { - if (strpos($customField, 'custom_') === 0) { - if (($this->_defaults[$customField] ?? '') === $newCustomValue) { + foreach ($params as $fieldKey => $newCustomValue) { + if (str_starts_with($fieldKey, 'custom_')) { + if (($this->_defaults[$fieldKey] ?? '') === $newCustomValue) { // Don't show values that did not change continue; } // We need custom field ID from custom_XX_1 - [, $customFieldId] = explode('_', $customField); + [, $customFieldId] = explode('_', $fieldKey); if (!empty($customFieldId) && is_numeric($customFieldId)) { // Got a custom field ID - $label = civicrm_api3('CustomField', 'getvalue', ['id' => $customFieldId, 'return' => 'label']); + $customField = CRM_Core_BAO_CustomField::getField($customFieldId); + $label = $customField['label']; // Convert dropdown and other machine values to human labels. // Money is special for non-US locales because at this point it's in human format so we don't // want to try to convert it. - $oldValue = $this->_defaults[$customField] ?? ''; + $oldValue = $this->_defaults[$fieldKey] ?? ''; $newValue = $newCustomValue; - if ('Money' !== (string) civicrm_api3('CustomField', 'getvalue', ['id' => $customFieldId, 'return' => 'data_type'])) { + if ('Money' !== $customField['data_type']) { $oldValue = civicrm_api3('CustomValue', 'getdisplayvalue', [ 'custom_field_id' => $customFieldId, 'entity_id' => $this->_entityID, diff --git a/www/modules/civicrm/CRM/Case/Form/Report.php b/www/modules/civicrm/CRM/Case/Form/Report.php index d28648431..7e5360af1 100644 --- a/www/modules/civicrm/CRM/Case/Form/Report.php +++ b/www/modules/civicrm/CRM/Case/Form/Report.php @@ -42,6 +42,8 @@ class CRM_Case_Form_Report extends CRM_Core_Form { /** * Build the form object. + * + * @throws \CRM_Core_Exception */ public function preProcess() { $this->_caseID = CRM_Utils_Request::retrieve('caseid', 'Integer', $this, TRUE); @@ -50,7 +52,7 @@ public function preProcess() { $this->_report = $this->get('report'); if ($this->_report) { - $this->assign_by_ref('report', $this->_report); + $this->assign('report', $this->_report); } // user context diff --git a/www/modules/civicrm/CRM/Case/Info.php b/www/modules/civicrm/CRM/Case/Info.php index 7f1e548c4..e36287dbc 100644 --- a/www/modules/civicrm/CRM/Case/Info.php +++ b/www/modules/civicrm/CRM/Case/Info.php @@ -42,42 +42,30 @@ public function getInfo() { /** * @inheritDoc - * @param bool $getAllUnconditionally - * @param bool $descriptions - * Whether to return permission descriptions - * - * @return array */ - public function getPermissions($getAllUnconditionally = FALSE, $descriptions = FALSE) { + public function getPermissions(): array { $permissions = [ 'delete in CiviCase' => [ - ts('delete in CiviCase'), - ts('Delete cases'), + 'label' => ts('delete in CiviCase'), + 'description' => ts('Delete cases'), ], 'administer CiviCase' => [ - ts('administer CiviCase'), - ts('Define case types, access deleted cases'), + 'label' => ts('administer CiviCase'), + 'description' => ts('Define case types, access deleted cases'), ], 'access my cases and activities' => [ - ts('access my cases and activities'), - ts('View and edit only those cases managed by this user'), + 'label' => ts('access my cases and activities'), + 'description' => ts('View and edit only those cases managed by this user'), ], 'access all cases and activities' => [ - ts('access all cases and activities'), - ts('View and edit all cases (for visible contacts)'), + 'label' => ts('access all cases and activities'), + 'description' => ts('View and edit all cases (for visible contacts)'), ], 'add cases' => [ - ts('add cases'), - ts('Open a new case'), + 'label' => ts('add cases'), + 'description' => ts('Open a new case'), ], ]; - - if (!$descriptions) { - foreach ($permissions as $name => $attr) { - $permissions[$name] = array_shift($attr); - } - } - return $permissions; } diff --git a/www/modules/civicrm/CRM/Case/ManagedEntities.php b/www/modules/civicrm/CRM/Case/ManagedEntities.php index 6c6d3d1f8..ec302e7be 100644 --- a/www/modules/civicrm/CRM/Case/ManagedEntities.php +++ b/www/modules/civicrm/CRM/Case/ManagedEntities.php @@ -43,7 +43,7 @@ public static function createManagedCaseTypes() { 'description' => (string) $xml->description, 'is_reserved' => 1, 'is_active' => 1, - 'weight' => $xml->weight ? $xml->weight : 1, + 'weight' => $xml->weight ?: 1, ], ]; } diff --git a/www/modules/civicrm/CRM/Case/Selector/Search.php b/www/modules/civicrm/CRM/Case/Selector/Search.php index a55665fc3..9e777fafd 100644 --- a/www/modules/civicrm/CRM/Case/Selector/Search.php +++ b/www/modules/civicrm/CRM/Case/Selector/Search.php @@ -335,7 +335,7 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $result->case_id ); - $row['contact_type'] = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ? $result->contact_sub_type : $result->contact_type + $row['contact_type'] = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ?: $result->contact_type ); //adding case manager to case selector.CRM-4510. diff --git a/www/modules/civicrm/CRM/Case/WorkflowMessage/ActivityExamples.php b/www/modules/civicrm/CRM/Case/WorkflowMessage/ActivityExamples.php new file mode 100644 index 000000000..47fb4e755 --- /dev/null +++ b/www/modules/civicrm/CRM/Case/WorkflowMessage/ActivityExamples.php @@ -0,0 +1,69 @@ + 'workflow/' . $workflow . '/activity' . (bool) $caseID, + 'title' => $caseID ? ts('Case Activity') : ts('Activity'), + 'tags' => ['preview'], + 'workflow' => $workflow, + 'case_id' => $caseID, + ]; + } + } + } + + /** + * Build an example to use when rendering the workflow. + * + * @param array $example + * + * @throws \API_Exception + */ + public function build(array &$example): void { + $workFlow = WorkflowMessage::get(TRUE)->addWhere('name', '=', $example['workflow'])->execute()->first(); + $this->setWorkflowName($workFlow['name']); + $messageTemplate = new $workFlow['class'](); + $this->addExampleData($messageTemplate, $example); + $example['data'] = $this->toArray($messageTemplate); + } + + /** + * Add relevant example data. + * + * @param \CRM_Case_WorkflowMessage_CaseActivity $messageTemplate + * @param array $example + * + * @throws \API_Exception + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception + * @throws \Civi\API\Exception\UnauthorizedException + */ + private function addExampleData(CRM_Case_WorkflowMessage_CaseActivity $messageTemplate, array $example): void { + $messageTemplate->setContact(\Civi\Test::example('entity/Contact/Barb')); + $messageTemplate->setActivity([ + 'activity_type_id:label' => ts('Follow up'), + // Ideally something better but let's not add more strings until we hae a good one. + 'subject' => ('Follow up'), + ]); + if (!empty($example['case_id'])) { + $messageTemplate->setCase(['subject' => ts('Sample case')]); + } + } + +} diff --git a/www/modules/civicrm/CRM/Case/WorkflowMessage/CaseActivity.php b/www/modules/civicrm/CRM/Case/WorkflowMessage/CaseActivity.php new file mode 100644 index 000000000..0a97a41eb --- /dev/null +++ b/www/modules/civicrm/CRM/Case/WorkflowMessage/CaseActivity.php @@ -0,0 +1,90 @@ +activity = $activity; + if (!empty($activity['id'])) { + $this->activityID = $activity['id']; + } + return $this; + } + + /** + * @param array $case + * + * @return CRM_Case_WorkflowMessage_CaseActivity + */ + public function setCase(array $case): CRM_Case_WorkflowMessage_CaseActivity { + $this->case = $case; + if (!empty($case['id'])) { + $this->caseID = $case['id']; + } + return $this; + } + +} diff --git a/www/modules/civicrm/CRM/Case/XMLProcessor/Report.php b/www/modules/civicrm/CRM/Case/XMLProcessor/Report.php index 6e42fb4bf..2f0f4a988 100644 --- a/www/modules/civicrm/CRM/Case/XMLProcessor/Report.php +++ b/www/modules/civicrm/CRM/Case/XMLProcessor/Report.php @@ -753,7 +753,7 @@ public static function populateCaseReportTemplate($clientID, $caseID, $activityS // first get all case information $case = $form->caseInfo($clientID, $caseID); - $template->assign_by_ref('case', $case); + $template->assign('case', $case); if (($params['include_activities'] ?? NULL) == 1) { $template->assign('includeActivities', 'All'); @@ -785,12 +785,12 @@ public static function populateCaseReportTemplate($clientID, $caseID, $activityS 'includeActivities' => 'All', 'redact' => 'false', ]; - $template->assign_by_ref('activitySet', $activitySet); + $template->assign('activitySet', $activitySet); //now collect all the information about activities $activities = []; $form->getActivities($clientID, $caseID, $activityTypes, $activities); - $template->assign_by_ref('activities', $activities); + $template->assign('activities', $activities); return $template; } @@ -959,7 +959,7 @@ public static function printCaseReport() { // Retrieve custom values for cases. $customValues = CRM_Core_BAO_CustomValueTable::getEntityValues($caseID, 'Case'); - $extends = ['case']; + $extends = ['Case']; $groupTree = CRM_Core_BAO_CustomGroup::getGroupDetail(NULL, NULL, $extends); $caseCustomFields = []; foreach ($groupTree as $gid => $group_values) { diff --git a/www/modules/civicrm/CRM/Contact/AccessTrait.php b/www/modules/civicrm/CRM/Contact/AccessTrait.php index efd40ee09..9c952aa78 100644 --- a/www/modules/civicrm/CRM/Contact/AccessTrait.php +++ b/www/modules/civicrm/CRM/Contact/AccessTrait.php @@ -15,30 +15,33 @@ * @copyright CiviCRM LLC https://civicrm.org/licensing */ +use Civi\Api4\Event\AuthorizeRecordEvent; +use Civi\Api4\Utils\CoreUtil; + /** * Trait shared with entities attached to the contact record. */ trait CRM_Contact_AccessTrait { /** - * @param string $entityName - * @param string $action - * @param array $record - * @param int $userID - * @return bool * @see \Civi\Api4\Utils\CoreUtil::checkAccessRecord */ - public static function _checkAccess(string $entityName, string $action, array $record, int $userID) { + public static function self_civi_api4_authorizeRecord(AuthorizeRecordEvent $e): void { + $record = $e->getRecord(); + $userID = $e->getUserID(); + $delegateAction = $e->getActionName() === 'get' ? 'get' : 'update'; $cid = $record['contact_id'] ?? NULL; if (!$cid && !empty($record['id'])) { $cid = CRM_Core_DAO::getFieldValue(__CLASS__, $record['id'], 'contact_id'); } if (!$cid) { // With no contact id this must be part of an event locblock - return in_array(__CLASS__, ['CRM_Core_BAO_Phone', 'CRM_Core_BAO_Email', 'CRM_Core_BAO_Address']) && - CRM_Core_Permission::check('edit all events', $userID); + $e->setAuthorized(in_array(__CLASS__, ['CRM_Core_BAO_Phone', 'CRM_Core_BAO_Email', 'CRM_Core_BAO_Address']) && + CRM_Core_Permission::check('edit all events', $userID)); + } + else { + $e->setAuthorized(CoreUtil::checkAccessDelegated('Contact', $delegateAction, ['id' => $cid], $userID)); } - return \Civi\Api4\Utils\CoreUtil::checkAccessDelegated('Contact', 'update', ['id' => $cid], $userID); } } diff --git a/www/modules/civicrm/CRM/Contact/ActionMapping.php b/www/modules/civicrm/CRM/Contact/ActionMapping.php index ca2788fad..af325a864 100644 --- a/www/modules/civicrm/CRM/Contact/ActionMapping.php +++ b/www/modules/civicrm/CRM/Contact/ActionMapping.php @@ -36,7 +36,7 @@ public function getEntityName(): string { return 'Contact'; } - public function modifySpec(\Civi\Api4\Service\Spec\RequestSpec $spec) { + public function modifyApiSpec(\Civi\Api4\Service\Spec\RequestSpec $spec) { $spec->getFieldByName('entity_value') ->setLabel(ts('Date Field')) ->setInputAttr('multiple', FALSE); @@ -47,15 +47,18 @@ public function modifySpec(\Civi\Api4\Service\Spec\RequestSpec $spec) { } public function getValueLabels(): array { - $allCustomFields = \CRM_Core_BAO_CustomField::getFields(''); + $filter = ['extends' => 'Contact', 'is_multiple' => FALSE, 'is_active' => TRUE]; + $contactCustomGroups = \CRM_Core_BAO_CustomGroup::getAll($filter); $dateFields = [ 'birth_date' => ts('Birth Date'), 'created_date' => ts('Created Date'), 'modified_date' => ts('Modified Date'), ]; - foreach ($allCustomFields as $fieldID => $field) { - if ($field['data_type'] == 'Date') { - $dateFields["custom_$fieldID"] = $field['label']; + foreach ($contactCustomGroups as $customGroup) { + foreach ($customGroup['fields'] as $field) { + if ($field['data_type'] == 'Date') { + $dateFields["custom_{$field['id']}"] = $field['label']; + } } } return $dateFields; diff --git a/www/modules/civicrm/CRM/Contact/BAO/Contact.php b/www/modules/civicrm/CRM/Contact/BAO/Contact.php index 253b4d023..424ca4998 100644 --- a/www/modules/civicrm/CRM/Contact/BAO/Contact.php +++ b/www/modules/civicrm/CRM/Contact/BAO/Contact.php @@ -9,6 +9,8 @@ +--------------------------------------------------------------------+ */ +use Civi\Api4\Contact; +use Civi\Api4\Event\AuthorizeRecordEvent; use Civi\Token\TokenProcessor; /** @@ -757,10 +759,6 @@ protected static function contactTrash($contact): bool { ]; CRM_Utils_Hook::pre('edit', $contact->contact_type, $contact->id, $updateParams); - $params = [1 => [$contact->id, 'Integer']]; - $query = 'DELETE FROM civicrm_uf_match WHERE contact_id = %1'; - CRM_Core_DAO::executeQuery($query, $params); - $contact->copyValues($updateParams); $contact->save(); CRM_Core_BAO_Log::register($contact->id, 'civicrm_contact', $contact->id); @@ -913,6 +911,15 @@ public static function deleteContact($id, $restore = FALSE, $skipUndelete = FALS return FALSE; } + // Note: we're not using CRM_Core_BAO_UFMatch::getUFId() because that's cached. + $ufmatch = new CRM_Core_DAO_UFMatch(); + $ufmatch->contact_id = $id; + $ufmatch->domain_id = CRM_Core_Config::domainID(); + if ($ufmatch->find(TRUE)) { + // Do not permit a contact to be deleted if it is linked to a site user. + return FALSE; + } + $contactType = $contact->contact_type; if ($restore) { // @todo deprecate calling contactDelete with the intention to restore. @@ -1145,7 +1152,11 @@ public static function processImage() { // $controller is not used at all but we need the CRM_Core_Controller object as in it's constructor // It retrieves the qfKey from GET or POST and then passes it to CRM_Core_Key::validate the generated key and redirects to a standard error message if fails $controller = new CRM_Core_Controller_Simple($formName, ts('New Contact'), NULL, TRUE, FALSE); - if (!CRM_Contact_BAO_Contact::_checkAccess('Contact', 'update', ['id' => $cid], NULL)) { + + if (!Contact::checkAccess() + ->setAction('update') + ->addValue('id', $cid) + ->execute()->first()['access']) { CRM_Utils_System::permissionDenied(); } CRM_Contact_BAO_Contact::deleteContactImage($cid); @@ -2651,7 +2662,7 @@ public static function getCountComponent(string $type, int $contactId, ?string $ default: if (!$tableName) { $custom = explode('_', $type); - $tableName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $custom[1], 'table_name'); + $tableName = CRM_Core_BAO_CustomGroup::getGroup(['id' => $custom[1]])['table_name']; } $queryString = "SELECT count(id) FROM $tableName WHERE entity_id = $contactId"; return (int) CRM_Core_DAO::singleValueQuery($queryString); @@ -3361,7 +3372,7 @@ public static function on_hook_civicrm_post(\Civi\Core\Event\PostEvent $event) { !empty($event->object->is_primary) && !empty($event->object->contact_id) ) { - $daoClass = CRM_Core_DAO_AllCoreTables::getFullName($event->entity); + $daoClass = CRM_Core_DAO_AllCoreTables::getDAONameForEntity($event->entity); $dao = new $daoClass(); $dao->contact_id = $event->object->contact_id; $dao->is_primary = 1; @@ -3592,17 +3603,17 @@ public static function getEntityRefFilters() { } /** - * @param string $entityName - * @param string $action - * @param array $record - * @param $userID - * @return bool + * Check contact access. * @see \Civi\Api4\Utils\CoreUtil::checkAccessRecord */ - public static function _checkAccess(string $entityName, string $action, array $record, $userID): bool { - switch ($action) { + public static function self_civi_api4_authorizeRecord(AuthorizeRecordEvent $e): void { + $record = $e->getRecord(); + $userID = $e->getUserID(); + + switch ($e->getActionName()) { case 'create': - return CRM_Core_Permission::check('add contacts', $userID); + $e->setAuthorized(CRM_Core_Permission::check('add contacts', $userID)); + return; case 'get': $actionType = CRM_Core_Permission::VIEW; @@ -3617,7 +3628,7 @@ public static function _checkAccess(string $entityName, string $action, array $r break; } - return CRM_Contact_BAO_Contact_Permission::allow($record['id'], $actionType, $userID); + $e->setAuthorized(CRM_Contact_BAO_Contact_Permission::allow($record['id'], $actionType, $userID)); } /** @@ -3631,7 +3642,11 @@ public static function _checkAccess(string $entityName, string $action, array $r * Id of the contact. * @throws CRM_Core_Exception */ - public static function getEntityIcon(string $entityName, int $entityId) { + public static function getEntityIcon(string $entityName, int $entityId = NULL): ?string { + $default = parent::getEntityIcon($entityName); + if (!$entityId) { + return $default; + } $contactTypes = CRM_Contact_BAO_ContactType::getAllContactTypes(); $subTypes = CRM_Utils_Array::explodePadded(CRM_Core_DAO::getFieldValue(parent::class, $entityId, 'contact_sub_type')); foreach ((array) $subTypes as $subType) { @@ -3641,7 +3656,7 @@ public static function getEntityIcon(string $entityName, int $entityId) { } // If no sub-type icon, lookup contact type $contactType = CRM_Core_DAO::getFieldValue(parent::class, $entityId, 'contact_type'); - return $contactTypes[$contactType]['icon'] ?? self::$_icon; + return $contactTypes[$contactType]['icon'] ?? $default; } } diff --git a/www/modules/civicrm/CRM/Contact/BAO/ContactType.php b/www/modules/civicrm/CRM/Contact/BAO/ContactType.php index b0b44710f..0985a35af 100644 --- a/www/modules/civicrm/CRM/Contact/BAO/ContactType.php +++ b/www/modules/civicrm/CRM/Contact/BAO/ContactType.php @@ -9,6 +9,8 @@ +--------------------------------------------------------------------+ */ +use Civi\Api4\Event\AuthorizeRecordEvent; + /** * * @package CRM @@ -42,35 +44,27 @@ public static function isActive($contactType) { } /** - * Retrieve basic contact type information. + * Retrieve base contact type information. * * @param bool $includeInactive * - * @return array - * Array of basic contact types information. - * - * @throws \CRM_Core_Exception - * @throws \Civi\API\Exception\UnauthorizedException + * @return array[] + * Array of base contact types keyed by name. */ - public static function basicTypeInfo($includeInactive = FALSE) { + public static function basicTypeInfo($includeInactive = FALSE): array { return array_filter(self::getAllContactTypes(), function($type) use ($includeInactive) { return empty($type['parent']) && ($includeInactive || $type['is_active']); }); } /** - * Retrieve all basic contact types. - * - * @param bool $all - * - * @return array - * Array of basic contact types + * Get names of base contact types e.g. [Individual, Household, Organization] * - * @throws \CRM_Core_Exception - * @throws \Civi\API\Exception\UnauthorizedException + * @param bool $includeInactive + * @return string[] */ - public static function basicTypes($all = FALSE): array { - return array_keys(self::basicTypeInfo($all)); + public static function basicTypes($includeInactive = FALSE): array { + return array_keys(self::basicTypeInfo($includeInactive)); } /** @@ -244,81 +238,43 @@ public static function contactTypePairs($all = FALSE, $typeName = NULL, $delimit * @param bool $isSeparator * @param string $separator * - * @return mixed - * @throws \Civi\Core\Exception\DBQueryException + * @return array */ public static function getSelectElements( $all = FALSE, $isSeparator = TRUE, $separator = '__' - ) { - // @todo - use Cache class - ie like Civi::cache('contactTypes') - static $_cache = NULL; - - if ($_cache === NULL) { - $_cache = []; - } - - // @todo - call getAllContactTypes & return filtered results. - $argString = $all ? 'CRM_CT_GSE_1' : 'CRM_CT_GSE_0'; - $argString .= $isSeparator ? '_1' : '_0'; - $argString .= $separator; - $argString = CRM_Utils_Cache::cleanKey($argString); - if (!array_key_exists($argString, $_cache)) { - $cache = CRM_Utils_Cache::singleton(); - $_cache[$argString] = $cache->get($argString); - - if (!$_cache[$argString]) { - $_cache[$argString] = []; - - $sql = ' -SELECT c.name as child_name , c.label as child_label , c.id as child_id, - p.name as parent_name, p.label as parent_label, p.id as parent_id -FROM civicrm_contact_type c -LEFT JOIN civicrm_contact_type p ON ( c.parent_id = p.id ) -WHERE ( c.name IS NOT NULL ) -'; - - if ($all === FALSE) { - $sql .= ' -AND c.is_active = 1 -AND ( p.is_active = 1 OR p.id IS NULL ) -'; - } - $sql .= " ORDER BY c.id"; - - $values = []; - $dao = CRM_Core_DAO::executeQuery($sql); - while ($dao->fetch()) { - if (!empty($dao->parent_id)) { - $key = $isSeparator ? $dao->parent_name . $separator . $dao->child_name : $dao->child_name; - $label = "- {$dao->child_label}"; - $pName = $dao->parent_name; - } - else { - $key = $dao->child_name; - $label = $dao->child_label; - $pName = $dao->child_name; - } - - if (!isset($values[$pName])) { - $values[$pName] = []; - } - $values[$pName][] = ['key' => $key, 'label' => $label]; - } + ): array { + $contactTypes = self::getAllContactTypes(); + foreach ($contactTypes as $contactType) { + $parent = $contactType['parent'] ? $contactTypes[$contactType['parent']] : NULL; + if (!$all && (!$contactType['is_active'] || ($parent && !$parent['is_active']))) { + continue; + } + if ($parent) { + $key = $isSeparator ? $parent['name'] . $separator . $contactType['name'] : $contactType['name']; + $label = "- {$contactType['label']}"; + $pName = $parent['name']; + } + else { + $key = $contactType['name']; + $label = $contactType['label']; + $pName = $contactType['name']; + } - $selectElements = []; - foreach ($values as $pName => $elements) { - foreach ($elements as $element) { - $selectElements[$element['key']] = $element['label']; - } - } - $_cache[$argString] = $selectElements; + if (!isset($values[$pName])) { + $values[$pName] = []; + } + $values[$pName][] = ['key' => $key, 'label' => $label]; + } - $cache->set($argString, $_cache[$argString]); + $selectElements = []; + foreach ($values as $elements) { + foreach ($elements as $element) { + $selectElements[$element['key']] = $element['label']; } } - return $_cache[$argString]; + return $selectElements; } /** @@ -339,40 +295,15 @@ public static function isaSubType($subType, $ignoreCache = FALSE) { * Retrieve the basic contact type associated with given subType. * * @param array|string $subType contact subType. - * @return array|string + * @return array|string|null + * Return value will be a string if input is a string, otherwise an array */ public static function getBasicType($subType) { - // @todo - use Cache class - ie like Civi::cache('contactTypes') - static $_cache = NULL; - if ($_cache === NULL) { - $_cache = []; - } - - $isArray = TRUE; - if ($subType && !is_array($subType)) { - $subType = [$subType]; - $isArray = FALSE; + $allSubTypes = array_column(self::subTypeInfo(NULL, TRUE), 'parent', 'name'); + if (is_array($subType)) { + return array_intersect_key($allSubTypes, array_flip($subType)); } - $argString = implode('_', $subType); - - if (!array_key_exists($argString, $_cache)) { - $_cache[$argString] = []; - - $sql = " -SELECT subtype.name as contact_subtype, type.name as contact_type -FROM civicrm_contact_type subtype -INNER JOIN civicrm_contact_type type ON ( subtype.parent_id = type.id ) -WHERE subtype.name IN ('" . implode("','", $subType) . "' )"; - $dao = CRM_Core_DAO::executeQuery($sql); - while ($dao->fetch()) { - if (!$isArray) { - $_cache[$argString] = $dao->contact_type; - break; - } - $_cache[$argString][$dao->contact_subtype] = $dao->contact_type; - } - } - return $_cache[$argString]; + return $allSubTypes[$subType] ?? NULL; } /** @@ -480,13 +411,15 @@ public static function self_hook_civicrm_pre(\Civi\Core\Event\PreEvent $event) { // Before deleting a contactType, check references by custom groups if ($event->action === 'delete') { $name = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_ContactType', $event->id); - $sep = CRM_Core_DAO::VALUE_SEPARATOR; - $custom = new CRM_Core_DAO_CustomGroup(); - $custom->whereAdd("extends_entity_column_value LIKE '%{$sep}{$name}{$sep}%'"); - if ($custom->find()) { - throw new CRM_Core_Exception(ts("You can not delete this contact type -- it is used by %1 custom field group(s). The custom fields must be deleted first.", [1 => $custom->N])); + $customGroups = CRM_Core_BAO_CustomGroup::getAll([ + 'extends' => 'Contact', + 'extends_entity_column_value' => $name, + ]); + if ($customGroups) { + throw new CRM_Core_Exception(ts("You can not delete this contact type -- it is used by %1 custom field group(s). The custom fields must be deleted first.", [1 => count($customGroups)])); } } + Civi::cache('contactTypes')->clear(); } /** @@ -631,27 +564,25 @@ public static function isAllowEdit($contactId, $subType = NULL) { } /** + * Checks to see if a given contact has custom data specific to a particular sub-type. + * * @param string $contactType * @param int $contactId * * @return bool */ public static function hasCustomData($contactType, $contactId = NULL) { - $subTypeClause = ''; - + $filters = [ + 'extends' => [$contactType], + ]; if (self::isaSubType($contactType)) { - $subType = $contactType; - $contactType = self::getBasicType($subType); - - // check for empty custom data which extends subtype - $subTypeValue = CRM_Core_DAO::VALUE_SEPARATOR . $subType . CRM_Core_DAO::VALUE_SEPARATOR; - $subTypeClause = " AND extends_entity_column_value LIKE '%{$subTypeValue}%' "; + $filters['extends'] = [self::getBasicType($contactType)]; + $filters['extends_entity_column_value'] = [$contactType]; } - $query = "SELECT table_name FROM civicrm_custom_group WHERE extends = '{$contactType}' {$subTypeClause}"; + $customGroups = CRM_Core_BAO_CustomGroup::getAll($filters); - $dao = CRM_Core_DAO::executeQuery($query); - while ($dao->fetch()) { - $sql = "SELECT count(id) FROM {$dao->table_name}"; + foreach ($customGroups as $customGroup) { + $sql = "SELECT count(id) FROM {$customGroup['table_name']}"; if ($contactId) { $sql .= " WHERE entity_id = {$contactId}"; } @@ -774,7 +705,7 @@ public static function deleteCustomRowsOfSubtype($gID, $subtypes = [], $subtypes return FALSE; } - $tableName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $gID, 'table_name'); + $tableName = CRM_Core_BAO_CustomGroup::getGroup(['id' => $gID])['table_name']; // drop triggers CRM-13587 CRM_Core_DAO::dropTriggers($tableName); @@ -828,10 +759,9 @@ public static function deleteCustomRowsForEntityID($customTable, $entityID) { * Note, this function is used within APIv4 Entity.get, so must use a * SQL query instead of calling APIv4 to avoid an infinite loop. * - * @return array - * @throws \CRM_Core_Exception + * @return array[] */ - public static function getAllContactTypes() { + public static function getAllContactTypes(): array { $cache = Civi::cache('contactTypes'); $cacheKey = 'all_' . $GLOBALS['tsLocale']; $contactTypes = $cache->get($cacheKey); @@ -842,15 +772,13 @@ public static function getAllContactTypes() { $dao = CRM_Core_DAO::executeQuery($query->toSQL()); $contactTypes = array_column($dao->fetchAll(), NULL, 'name'); $parents = array_column($contactTypes, NULL, 'id'); - foreach ($contactTypes as $name => $contactType) { - $contactTypes[$name]['parent'] = $contactType['parent_id'] ? $parents[$contactType['parent_id']]['name'] : NULL; - $contactTypes[$name]['parent_label'] = $contactType['parent_id'] ? $parents[$contactType['parent_id']]['label'] : NULL; + foreach ($contactTypes as &$contactType) { // Cast int/bool types. - $contactTypes[$name]['id'] = (int) $contactType['id']; - $contactTypes[$name]['parent_id'] = ((int) $contactType['parent_id']) ?: NULL; - $contactTypes[$name]['is_active'] = (bool) $contactType['is_active']; - $contactTypes[$name]['is_reserved'] = (bool) $contactType['is_reserved']; - $contactTypes[$name]['icon'] = $contactType['icon'] ?? $parents[$contactType['parent_id']]['icon'] ?? NULL; + self::formatFieldValues($contactType); + // Fill data from parents + $contactType['parent'] = $parents[$contactType['parent_id']]['name'] ?? NULL; + $contactType['parent_label'] = $parents[$contactType['parent_id']]['label'] ?? NULL; + $contactType['icon'] ??= $parents[$contactType['parent_id']]['icon'] ?? NULL; } $cache->set($cacheKey, $contactTypes); } @@ -868,23 +796,18 @@ public static function getContactType(string $name): ?array { } /** - * @param string $entityName - * @param string $action - * @param array $record - * @param $userID - * @return bool + * Check write access. * @see \Civi\Api4\Utils\CoreUtil::checkAccessRecord */ - public static function _checkAccess(string $entityName, string $action, array $record, $userID): bool { + public static function self_civi_api4_authorizeRecord(AuthorizeRecordEvent $e): void { // Only records with a parent may be deleted - if ($action === 'delete') { + if ($e->getActionName() === 'delete') { + $record = $e->getRecord(); if (!array_key_exists('parent_id', $record)) { $record['parent_id'] = CRM_Core_DAO::getFieldValue(parent::class, $record['id'], 'parent_id'); } - return (bool) $record['parent_id']; + $e->setAuthorized((bool) $record['parent_id']); } - // Gatekeeper permissions suffice for everything else - return TRUE; } } diff --git a/www/modules/civicrm/CRM/Contact/BAO/Group.php b/www/modules/civicrm/CRM/Contact/BAO/Group.php index 4fb0a5d68..5e0a0f5f1 100644 --- a/www/modules/civicrm/CRM/Contact/BAO/Group.php +++ b/www/modules/civicrm/CRM/Contact/BAO/Group.php @@ -9,14 +9,16 @@ +--------------------------------------------------------------------+ */ +use Civi\Api4\Event\AuthorizeRecordEvent; use Civi\Api4\Group; +use Civi\Core\HookInterface; /** * * @package CRM * @copyright CiviCRM LLC https://civicrm.org/licensing */ -class CRM_Contact_BAO_Group extends CRM_Contact_DAO_Group { +class CRM_Contact_BAO_Group extends CRM_Contact_DAO_Group implements HookInterface { /** * @deprecated @@ -710,8 +712,8 @@ public static function createHiddenSmartGroup($params) { //create group only when new saved search. $groupParams = [ 'title' => "Hidden Smart Group {$ssId}", - 'is_active' => CRM_Utils_Array::value('is_active', $params, 1), - 'is_hidden' => CRM_Utils_Array::value('is_hidden', $params, 1), + 'is_active' => $params['is_active'] ?? 1, + 'is_hidden' => $params['is_hidden'] ?? 1, 'group_type' => $params['group_type'] ?? NULL, 'visibility' => $params['visibility'] ?? NULL, 'saved_search_id' => $ssId, @@ -1441,32 +1443,26 @@ public static function filterActiveGroups($parentArray) { } /** - * @param string $entityName - * @param string $action - * @param array $record - * @param $userID - * @return bool + * Check write access. * @see \Civi\Api4\Utils\CoreUtil::checkAccessRecord */ - public static function _checkAccess(string $entityName, string $action, array $record, $userID): bool { - switch ($action) { - case 'create': - $groupType = (array) ($record['group_type:name'] ?? []); - // If not already in :name format, transform to name - foreach ((array) ($record['group_type'] ?? []) as $typeId) { - $groupType[] = CRM_Core_PseudoConstant::getName(self::class, 'group_type', $typeId); - } - if ($groupType === ['Mailing List']) { - // If it's only a Mailing List, edit groups OR create mailings will work - return CRM_Core_Permission::check(['access CiviCRM', ['edit groups', 'access CiviMail', 'create mailings']], $userID); - } - else { - return CRM_Core_Permission::check(['access CiviCRM', 'edit groups'], $userID); - } - - default: - // All other actions just rely on gatekeeper permissions - return TRUE; + public static function self_civi_api4_authorizeRecord(AuthorizeRecordEvent $e): void { + $record = $e->getRecord(); + $userID = $e->getUserID(); + // Check create permission (all other actions just rely on gatekeeper permissions) + if ($e->getActionName() === 'create') { + $groupType = (array) ($record['group_type:name'] ?? []); + // If not already in :name format, transform to name + foreach ((array) ($record['group_type'] ?? []) as $typeId) { + $groupType[] = CRM_Core_PseudoConstant::getName(self::class, 'group_type', $typeId); + } + if ($groupType === ['Mailing List']) { + // If it's only a Mailing List, edit groups OR create mailings will work + $e->setAuthorized(CRM_Core_Permission::check(['access CiviCRM', ['edit groups', 'access CiviMail', 'create mailings']], $userID)); + } + else { + $e->setAuthorized(CRM_Core_Permission::check(['access CiviCRM', 'edit groups'], $userID)); + } } } diff --git a/www/modules/civicrm/CRM/Contact/BAO/Individual.php b/www/modules/civicrm/CRM/Contact/BAO/Individual.php index fc375bb4a..ae8b21068 100644 --- a/www/modules/civicrm/CRM/Contact/BAO/Individual.php +++ b/www/modules/civicrm/CRM/Contact/BAO/Individual.php @@ -55,10 +55,10 @@ public static function format(&$params, $contact) { $firstName = trim($params['first_name'] ?? ''); $middleName = trim($params['middle_name'] ?? ''); $lastName = trim($params['last_name'] ?? ''); - $nickName = CRM_Utils_Array::value('nick_name', $params, ''); - $prefix_id = CRM_Utils_Array::value('prefix_id', $params, ''); - $suffix_id = CRM_Utils_Array::value('suffix_id', $params, ''); - $formalTitle = CRM_Utils_Array::value('formal_title', $params, ''); + $nickName = $params['nick_name'] ?? ''; + $prefix_id = $params['prefix_id'] ?? ''; + $suffix_id = $params['suffix_id'] ?? ''; + $formalTitle = $params['formal_title'] ?? ''; // get prefix and suffix names $params['prefix_id:label'] = $prefix = CRM_Core_PseudoConstant::getLabel('CRM_Contact_DAO_Contact', 'prefix_id', $prefix_id); @@ -73,15 +73,17 @@ public static function format(&$params, $contact) { //lets allow to update single name field though preserveDBName //but if db having null value and params contain value, CRM-4330. $useDBNames = []; - - foreach (['last', 'middle', 'first', 'nick'] as $name) { - $dbName = "{$name}_name"; - $value = $individual->$dbName; - - // the db has name values - if ($value && !empty($params['preserveDBName'])) { - $useDBNames[] = $name; - } + if ($individual->last_name && !empty($params['preserveDBName'])) { + $useDBNames[] = 'last'; + } + if ($individual->middle_name && !empty($params['preserveDBName'])) { + $useDBNames[] = 'middle'; + } + if ($individual->first_name && !empty($params['preserveDBName'])) { + $useDBNames[] = 'first'; + } + if ($individual->nick_name && !empty($params['preserveDBName'])) { + $useDBNames[] = 'nick'; } if ($individual->suffix_id && !empty($params['preserveDBName'])) { @@ -100,22 +102,52 @@ public static function format(&$params, $contact) { //1. preserve db name if want //2. lets get value from param if exists. //3. if not in params, lets get from db. + if (in_array('last', $useDBNames)) { + $params['last_name'] = $individual->last_name; + $contact->last_name = $individual->last_name; + $lastName = $individual->last_name; + } + elseif (array_key_exists('last_name', $params)) { + $lastName = $params['last_name']; + } + elseif ($individual->last_name) { + $lastName = $individual->last_name; + } - foreach (['last', 'middle', 'first', 'nick'] as $name) { - $phpName = "{$name}Name"; - $dbName = "{$name}_name"; - $value = $individual->$dbName; - if (in_array($name, $useDBNames)) { - $params[$dbName] = $value; - $contact->$dbName = $value; - $$phpName = $value; - } - elseif (array_key_exists($dbName, $params)) { - $$phpName = $params[$dbName]; - } - elseif ($value) { - $$phpName = $value; - } + if (in_array('middle', $useDBNames)) { + $params['middle_name'] = $individual->middle_name; + $contact->middle_name = $individual->middle_name; + $middleName = $individual->middle_name; + } + elseif (array_key_exists('middle_name', $params)) { + $middleName = $params['middle_name']; + } + elseif ($individual->middle_name) { + $middleName = $individual->middle_name; + } + + if (in_array('first', $useDBNames)) { + $params['first_name'] = $individual->first_name; + $contact->first_name = $individual->first_name; + $firstName = $individual->first_name; + } + elseif (array_key_exists('first_name', $params)) { + $firstName = $params['first_name']; + } + elseif ($individual->first_name) { + $firstName = $individual->first_name; + } + + if (in_array('nick', $useDBNames)) { + $params['nick_name'] = $individual->nick_name; + $contact->nick_name = $individual->nick_name; + $nickName = $individual->nick_name; + } + elseif (array_key_exists('nick_name', $params)) { + $nickName = $params['nick_name']; + } + elseif ($individual->nick_name) { + $nickName = $individual->nick_name; } foreach (['prefix', 'suffix'] as $name) { @@ -178,15 +210,6 @@ public static function format(&$params, $contact) { } } - $tokens = []; - CRM_Utils_Hook::tokens($tokens); - $tokenFields = []; - foreach ($tokens as $catTokens) { - foreach ($catTokens as $token => $label) { - $tokenFields[] = $token; - } - } - $formatted['id'] = $contact->id ?? $params['id'] ?? 0; $tokenProcessor = new TokenProcessor(\Civi::dispatcher(), [ 'class' => __CLASS__, diff --git a/www/modules/civicrm/CRM/Contact/BAO/Query.php b/www/modules/civicrm/CRM/Contact/BAO/Query.php index b4b6ff3d3..69cdae134 100644 --- a/www/modules/civicrm/CRM/Contact/BAO/Query.php +++ b/www/modules/civicrm/CRM/Contact/BAO/Query.php @@ -1276,10 +1276,6 @@ public function addHierarchicalElements() { //build locationType join $locationTypeJoin[$tName] = " ( `$tName`.location_type_id = $ltName.id )"; - - if ($addWhere) { - $this->_whereTables[$tName] = $this->_tables[$tName]; - } break; case 'civicrm_state_province': @@ -4580,6 +4576,8 @@ public static function getQuery($params = NULL, $returnProperties = NULL, $count * * @return array * @throws \CRM_Core_Exception + * + * @deprecated since 5.71 - will be removed after all core usages are fully removed. */ public static function apiQuery( $params = NULL, @@ -4766,9 +4764,8 @@ public static function isCustomDateField($fieldName) { return FALSE; } try { - $customFieldData = CRM_Core_BAO_CustomField::getFieldObject($customFieldID); - $customFieldDataType = $customFieldData->data_type; - if ('Date' == $customFieldDataType) { + $customFieldData = CRM_Core_BAO_CustomField::getField($customFieldID); + if ($customFieldData && 'Date' == $customFieldData['data_type']) { return TRUE; } } @@ -5502,7 +5499,7 @@ public function ageRangeQueryBuilder( $asofDateValues = $this->getWhereValues("{$fieldName}_asof_date", $grouping); // will be treated as current day - $asofDate = NULL; + $asofDate = ''; if ($asofDateValues) { $asofDate = CRM_Utils_Date::processDate($asofDateValues[2]); $asofDateFormat = CRM_Utils_Date::customFormat(substr($asofDate, 0, 8)); @@ -6887,7 +6884,7 @@ protected function getMetadataForField($fieldName) { if (!empty($field) && empty($field['name'])) { // standardising field formatting here - over time we can phase out variants. // all paths using this currently unit tested - $field['name'] = CRM_Utils_Array::value('field_name', $field, CRM_Utils_Array::value('idCol', $field, $fieldName)); + $field['name'] = $field['field_name'] ?? $field['idCol'] ?? $fieldName; } return $field; } diff --git a/www/modules/civicrm/CRM/Contact/BAO/Relationship.php b/www/modules/civicrm/CRM/Contact/BAO/Relationship.php index 98a29ff92..c87a090f4 100644 --- a/www/modules/civicrm/CRM/Contact/BAO/Relationship.php +++ b/www/modules/civicrm/CRM/Contact/BAO/Relationship.php @@ -9,6 +9,7 @@ +--------------------------------------------------------------------+ */ +use Civi\Api4\Event\AuthorizeRecordEvent; use Civi\Api4\MembershipType; use Civi\Api4\Relationship; @@ -157,7 +158,7 @@ public static function createMultiple($params, $primaryContactLetter) { * @throws \CRM_Core_Exception */ public static function add($params, $ids = []) { - $params['id'] = CRM_Utils_Array::value('relationship', $ids, CRM_Utils_Array::value('id', $params)); + $params['id'] = $ids['relationship'] ?? $params['id'] ?? NULL; $hook = 'create'; if ($params['id']) { @@ -2266,24 +2267,24 @@ protected static function updateMembershipsByRelationship(array $params, CRM_Con } /** - * @param string $entityName - * @param string $action - * @param array $record - * @param int $userID - * @return bool + * Check related contact access. * @see \Civi\Api4\Utils\CoreUtil::checkAccessRecord */ - public static function _checkAccess(string $entityName, string $action, array $record, int $userID): bool { + public static function self_civi_api4_authorizeRecord(AuthorizeRecordEvent $e): void { + $record = $e->getRecord(); + $userID = $e->getUserID(); + $delegateAction = $e->getActionName() === 'get' ? 'get' : 'update'; + // Delegate relationship permissions to contacts a & b foreach (['a', 'b'] as $ab) { if (empty($record["contact_id_$ab"]) && !empty($record['id'])) { $record["contact_id_$ab"] = CRM_Core_DAO::getFieldValue(__CLASS__, $record['id'], "contact_id_$ab"); } - if (!empty($record["contact_id_$ab"]) && !\Civi\Api4\Utils\CoreUtil::checkAccessDelegated('Contact', 'update', ['id' => $record["contact_id_$ab"]], $userID)) { - return FALSE; + if (!empty($record["contact_id_$ab"]) && !\Civi\Api4\Utils\CoreUtil::checkAccessDelegated('Contact', $delegateAction, ['id' => $record["contact_id_$ab"]], $userID)) { + $e->setAuthorized(FALSE); + break; } } - return TRUE; } } diff --git a/www/modules/civicrm/CRM/Contact/BAO/SavedSearch.php b/www/modules/civicrm/CRM/Contact/BAO/SavedSearch.php index bb7b939a4..36b6016f2 100644 --- a/www/modules/civicrm/CRM/Contact/BAO/SavedSearch.php +++ b/www/modules/civicrm/CRM/Contact/BAO/SavedSearch.php @@ -378,4 +378,40 @@ public static function getApiEntityOptions() { ->column('title_plural'); } + /** + * Gets all smart groups that filter based on groupID. + * + * @param int $groupID + * Group Id to search for. + * @return array + */ + public static function getSmartGroupsUsingGroup(int $groupID) { + $groups = \Civi\Api4\Group::get(FALSE) + ->addSelect('id', 'title', 'saved_search_id', 'saved_search_id.form_values') + ->addWhere('saved_search_id', 'IS NOT NULL') + ->addWhere('saved_search_id.form_values', 'CONTAINS', 'group') + ->setLimit(0) + ->execute(); + $smartGroups = []; + foreach ($groups as $group) { + // Filter out arrays in Form Values which are group searchs. + $groupSearches = array_filter( + $group['saved_search_id.form_values'], + function($v) { + return ($v[0] == 'group'); + } + ); + // Check each group search for valid groups. + foreach ($groupSearches as $groupSearch) { + if (!empty($groupSearch[2]) && in_array($groupID, $groupSearch[2])) { + $smartGroups[$group['id']] = [ + 'title' => $group['title'], + 'editSearchURL' => self::getEditSearchUrl($group['saved_search_id']), + ]; + } + } + } + return $smartGroups; + } + } diff --git a/www/modules/civicrm/CRM/Contact/DAO/ACLContactCache.php b/www/modules/civicrm/CRM/Contact/DAO/ACLContactCache.php index 457d6542a..5bae77597 100644 --- a/www/modules/civicrm/CRM/Contact/DAO/ACLContactCache.php +++ b/www/modules/civicrm/CRM/Contact/DAO/ACLContactCache.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contact/ACLContactCache.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:4c2e841e02a874dc937ee986f0346baa) + * (GenCodeChecksum:241b4822d3e48b4affef792d19ff6530) */ /** diff --git a/www/modules/civicrm/CRM/Contact/DAO/Contact.php b/www/modules/civicrm/CRM/Contact/DAO/Contact.php index 5dcd38587..0da7d1fb6 100644 --- a/www/modules/civicrm/CRM/Contact/DAO/Contact.php +++ b/www/modules/civicrm/CRM/Contact/DAO/Contact.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contact/Contact.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:f799748a5b7e54fafad9dea3cafbb444) + * (GenCodeChecksum:c2c4d9156d6646a4751d96b14b0b2ab8) */ /** diff --git a/www/modules/civicrm/CRM/Contact/DAO/ContactType.php b/www/modules/civicrm/CRM/Contact/DAO/ContactType.php index 26bc5c406..ee538e987 100644 --- a/www/modules/civicrm/CRM/Contact/DAO/ContactType.php +++ b/www/modules/civicrm/CRM/Contact/DAO/ContactType.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contact/ContactType.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:e6867c8178161a74d2aa6c0cd2f55b7a) + * (GenCodeChecksum:5c103aac8df396df5b3977b1ee2c8bc2) */ /** diff --git a/www/modules/civicrm/CRM/Contact/DAO/DashboardContact.php b/www/modules/civicrm/CRM/Contact/DAO/DashboardContact.php index 2b56d1455..d845f93a8 100644 --- a/www/modules/civicrm/CRM/Contact/DAO/DashboardContact.php +++ b/www/modules/civicrm/CRM/Contact/DAO/DashboardContact.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contact/DashboardContact.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:bdf36ed6404765eac4648f18ddd25e0c) + * (GenCodeChecksum:df244cd704a493c8a78aeebb30b8e0df) */ /** diff --git a/www/modules/civicrm/CRM/Contact/DAO/Group.php b/www/modules/civicrm/CRM/Contact/DAO/Group.php index b606f1849..9e773755d 100644 --- a/www/modules/civicrm/CRM/Contact/DAO/Group.php +++ b/www/modules/civicrm/CRM/Contact/DAO/Group.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contact/Group.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:d673dfbe94fef69bf9598fdf3020dda4) + * (GenCodeChecksum:921454d1f48dbba0feec6e1dcfd0fd76) */ /** diff --git a/www/modules/civicrm/CRM/Contact/DAO/GroupContact.php b/www/modules/civicrm/CRM/Contact/DAO/GroupContact.php index 42c2c1514..5c4fcd0ad 100644 --- a/www/modules/civicrm/CRM/Contact/DAO/GroupContact.php +++ b/www/modules/civicrm/CRM/Contact/DAO/GroupContact.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contact/GroupContact.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:b823eecee947f4102bccd38a2fa10d04) + * (GenCodeChecksum:84014664926735717c246755326674e3) */ /** diff --git a/www/modules/civicrm/CRM/Contact/DAO/GroupContactCache.php b/www/modules/civicrm/CRM/Contact/DAO/GroupContactCache.php index 0ec9b07a6..4c615a759 100644 --- a/www/modules/civicrm/CRM/Contact/DAO/GroupContactCache.php +++ b/www/modules/civicrm/CRM/Contact/DAO/GroupContactCache.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contact/GroupContactCache.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:a700f89a58ee1b7e63ca867bcbc1de86) + * (GenCodeChecksum:0b52e53d1cf34f8d1934380d0c397ad5) */ /** diff --git a/www/modules/civicrm/CRM/Contact/DAO/GroupNesting.php b/www/modules/civicrm/CRM/Contact/DAO/GroupNesting.php index edd3429fd..9aa9c6e6b 100644 --- a/www/modules/civicrm/CRM/Contact/DAO/GroupNesting.php +++ b/www/modules/civicrm/CRM/Contact/DAO/GroupNesting.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contact/GroupNesting.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:31b039d439208a08efb24155443a9241) + * (GenCodeChecksum:659840b7a7904852b3e352131af7d380) */ /** diff --git a/www/modules/civicrm/CRM/Contact/DAO/GroupOrganization.php b/www/modules/civicrm/CRM/Contact/DAO/GroupOrganization.php index 2651a4008..b6bc25d0a 100644 --- a/www/modules/civicrm/CRM/Contact/DAO/GroupOrganization.php +++ b/www/modules/civicrm/CRM/Contact/DAO/GroupOrganization.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contact/GroupOrganization.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:0154a5b5ddbc324b25eb18b18cee70b5) + * (GenCodeChecksum:f58ad0d8da6013e738677cd44c3b090f) */ /** diff --git a/www/modules/civicrm/CRM/Contact/DAO/Relationship.php b/www/modules/civicrm/CRM/Contact/DAO/Relationship.php index 0527cf724..6560945d7 100644 --- a/www/modules/civicrm/CRM/Contact/DAO/Relationship.php +++ b/www/modules/civicrm/CRM/Contact/DAO/Relationship.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contact/Relationship.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:cb178a4634f48ebf1240e74240100693) + * (GenCodeChecksum:31c1f7ff9fa679875c85b7bf5718a5ee) */ /** diff --git a/www/modules/civicrm/CRM/Contact/DAO/RelationshipCache.php b/www/modules/civicrm/CRM/Contact/DAO/RelationshipCache.php index 3351c6a2d..38df8f90c 100644 --- a/www/modules/civicrm/CRM/Contact/DAO/RelationshipCache.php +++ b/www/modules/civicrm/CRM/Contact/DAO/RelationshipCache.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contact/RelationshipCache.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:6d5a3561e59414ced7321ba51416886d) + * (GenCodeChecksum:1cdf537d7807c8b5fd801507f527dfcf) */ /** diff --git a/www/modules/civicrm/CRM/Contact/DAO/RelationshipType.php b/www/modules/civicrm/CRM/Contact/DAO/RelationshipType.php index 3b5083db5..52cca0c71 100644 --- a/www/modules/civicrm/CRM/Contact/DAO/RelationshipType.php +++ b/www/modules/civicrm/CRM/Contact/DAO/RelationshipType.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contact/RelationshipType.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:e729e1ecffeb6861f8dd1c7523612621) + * (GenCodeChecksum:4eb82c548def98f3a77769be7e391475) */ /** diff --git a/www/modules/civicrm/CRM/Contact/DAO/SavedSearch.php b/www/modules/civicrm/CRM/Contact/DAO/SavedSearch.php index 81a8ef381..a2e356dc5 100644 --- a/www/modules/civicrm/CRM/Contact/DAO/SavedSearch.php +++ b/www/modules/civicrm/CRM/Contact/DAO/SavedSearch.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contact/SavedSearch.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:6a4e86b2c7c5ad71a68b90c40cd5313f) + * (GenCodeChecksum:66051ccf8c8ea86bc43f759ca75c2106) */ /** diff --git a/www/modules/civicrm/CRM/Contact/DAO/SubscriptionHistory.php b/www/modules/civicrm/CRM/Contact/DAO/SubscriptionHistory.php index d3dae0265..d841bb884 100644 --- a/www/modules/civicrm/CRM/Contact/DAO/SubscriptionHistory.php +++ b/www/modules/civicrm/CRM/Contact/DAO/SubscriptionHistory.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contact/SubscriptionHistory.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:c2c9def531bf0ef05e40200f82a280e4) + * (GenCodeChecksum:8d80c4935192d6e7ac016fc43933da0f) */ /** diff --git a/www/modules/civicrm/CRM/Contact/Form/Contact.php b/www/modules/civicrm/CRM/Contact/Form/Contact.php index a7d93bc07..4153f6ee8 100644 --- a/www/modules/civicrm/CRM/Contact/Form/Contact.php +++ b/www/modules/civicrm/CRM/Contact/Form/Contact.php @@ -26,6 +26,7 @@ class CRM_Contact_Form_Contact extends CRM_Core_Form { use CRM_Contact_Form_ContactFormTrait; + use CRM_Custom_Form_CustomDataTrait; /** * The contact type of the form. @@ -279,7 +280,7 @@ public function preProcess() { } // build demographics only for Individual contact type - if ($this->_contactType != 'Individual' && + if ($this->_contactType !== 'Individual' && array_key_exists('Demographics', $this->_editOptions) ) { unset($this->_editOptions['Demographics']); @@ -313,62 +314,32 @@ public function preProcess() { $this->preProcessLocation(); - // retain the multiple count custom fields value - if (!empty($_POST['hidden_custom'])) { - $customGroupCount = $_POST['hidden_custom_group_count'] ?? NULL; - - $contactSubType = $_POST['contact_sub_type'] ?? NULL; - if ($contactSubType) { - $paramSubType = implode(',', $contactSubType); - } - - unset($customGroupCount[0]); - foreach ($customGroupCount as $groupID => $groupCount) { - if ($groupCount > 1) { - $this->set('groupID', $groupID); - //loop the group - for ($i = 1; $i <= $groupCount; $i++) { - CRM_Custom_Form_CustomData::preProcess($this, NULL, $contactSubType, - $i, $this->_contactType, $this->_contactId, NULL, FALSE - ); - CRM_Contact_Form_Edit_CustomData::buildQuickForm($this); - } - } - } - - //reset all the ajax stuff, for normal processing - if (isset($this->_groupTree)) { - $this->_groupTree = NULL; - } - $this->set('groupID', NULL); - $this->_getCachedTree = TRUE; + if ($this->isSubmitted()) { + // The custom data fields are added to the form by an ajax form. + // However, if they are not present in the element index they will + // not be available from `$this->getSubmittedValue()` in post process. + // We do not have to set defaults or otherwise render - just add to the element index. + $this->addCustomDataFieldsToForm('Contact', array_filter([ + 'id' => $this->getContactID(), + 'contact_type' => $this->_contactType, + 'contact_sub_type' => $this->getSubmittedValue('contact_sub_type'), + ])); } // execute preProcess dynamically by js else execute normal preProcess if (array_key_exists('CustomData', $this->_editOptions)) { //assign a parameter to pass for sub type multivalue //custom field to load + if ($this->isSubmitted()) { + $contactSubType = $_POST['contact_sub_type'] ?? NULL; + if ($contactSubType) { + $paramSubType = implode(',', $contactSubType); + } + } if ($this->_contactSubType || isset($paramSubType)) { $paramSubType = (isset($paramSubType)) ? $paramSubType : str_replace(CRM_Core_DAO::VALUE_SEPARATOR, ',', trim($this->_contactSubType, CRM_Core_DAO::VALUE_SEPARATOR)); } - - if (CRM_Utils_Request::retrieve('type', 'String')) { - CRM_Contact_Form_Edit_CustomData::preProcess($this); - } - else { - // The reason we call this here is that it sets the _groupTree property which is later used - // in setDefaultValues and buildForm. (Ideally instead we would have a trait with getCustomGroup & getCustomFields - // that can be called at appropriate times). In order for buildForm to add the right fields it needs - // to know any contact sub types that are being added in the submission. Since this runs before - // the buildForm adds the contact_sub_type to the form we need to look in _submitValues for it - submitValues - // is a un-sanitised version of what is in the form submission (_POST) whereas `getSubmittedValues()` retrieves - // 'allowed' POSTED values - ie values which match available fields, with some localization handling. - CRM_Custom_Form_CustomData::preProcess($this, NULL, $this->isSubmitted() ? ($this->_submitValues['contact_sub_type'] ?? []) : $this->getContactValue('contact_sub_type') ?? $this->_contactSubType, - 1, $this->_contactType, $this->getContactID() - ); - $this->assign('customValueCount', $this->_customValueCount); - } } $this->assign('paramSubType', $paramSubType ?? ''); } @@ -434,8 +405,7 @@ public function setDefaultValues() { // set defaults for blocks ( custom data, address, communication preference, notes, tags and groups ) foreach ($this->_editOptions as $name => $label) { if (!in_array($name, ['Address', 'Notes'])) { - $className = 'CRM_Contact_Form_Edit_' . $name; - $className::setDefaultValues($this, $defaults); + $this->setBlockDefaults($defaults, $name); } } @@ -767,8 +737,8 @@ public static function formRule($fields, &$errors, $contactId, $contactType) { public function buildQuickForm() { //load form for child blocks if ($this->isAjaxMode()) { - $className = 'CRM_Contact_Form_Edit_' . $this->getAjaxBlockName(); - return $className::buildQuickForm($this); + $this->buildLocationBlock($this->getAjaxBlockName()); + return; } if ($this->_action == CRM_Core_Action::UPDATE) { @@ -797,15 +767,32 @@ public function buildQuickForm() { } //build contact type specific fields - $className = 'CRM_Contact_Form_Edit_' . $this->_contactType; - $className::buildQuickForm($this); + $this->buildContactTypeSpecificFields(); // Ajax duplicate checking $checkSimilar = Civi::settings()->get('contact_ajax_check_similar'); $this->assign('checkSimilar', $checkSimilar); if ($checkSimilar == 1) { $ruleParams = ['used' => 'Supervised', 'contact_type' => $this->_contactType]; - $this->assign('ruleFields', CRM_Dedupe_BAO_DedupeRule::dedupeRuleFields($ruleParams)); + // These are the fields tht are passed to apiv3 Contact.getduplicates. + // Most likely it is enough to pass only the fields returned from the function but + // this code has been a bit flip-floppy with the ruleFields removed in favour of + // the hard-coded list below here + // https://github.com/civicrm/civicrm-core/commit/01ee39a0165a6f3fdc8b105626abaa9cb951bb3f#diff-eee833728c23038f8ac3e2bbb37bef5fa1f718d327896178ae9526c31ee80672L265-R273 + // at that point the api was switched here https://github.com/civicrm/civicrm-core/commit/01ee39a0165a6f3fdc8b105626abaa9cb951bb3f#diff-eee833728c23038f8ac3e2bbb37bef5fa1f718d327896178ae9526c31ee80672R311 + // However, it was at least partially switched back here https://github.com/civicrm/civicrm-core/commit/a7ba493ce752fee7b05daa103bc1c956b7d05b0f#diff-eee833728c23038f8ac3e2bbb37bef5fa1f718d327896178ae9526c31ee80672R334 + // so deliberately erring on the side of passing too much information in hopes of breaking + // the wheel. + $ruleFields = array_unique(CRM_Dedupe_BAO_DedupeRule::dedupeRuleFields($ruleParams) + + [ + 'first_name', + 'last_name', + 'nick_name', + 'household_name', + 'organization_name', + 'email', + ]); + $this->assign('ruleFields', json_encode($ruleFields)); } // build Custom data if Custom data present in edit option @@ -828,15 +815,14 @@ public function buildQuickForm() { // build edit blocks ( custom data, demographics, communication preference, notes, tags and groups ) foreach ($this->_editOptions as $name => $label) { - if ($name == 'Address') { + if ($name === 'Address') { $this->_blocks['Address'] = $this->_editOptions['Address']; continue; } - if ($name == 'TagsAndGroups') { + if ($name === 'TagsAndGroups') { continue; } - $className = 'CRM_Contact_Form_Edit_' . $name; - $className::buildQuickForm($this); + $this->buildBlock($name); } // build tags and groups @@ -956,11 +942,8 @@ private function buildLocationForm(): void { break; default: - // @todo This pattern actually adds complexity compared to filling out a switch statement - // for the limited number of blocks - as we also have to receive the block count $this->set($blockName . '_Block_Count', $instance); - $formName = 'CRM_Contact_Form_Edit_' . $blockName; - $formName::buildQuickForm($this); + $this->buildLocationBlock($blockName); } } } @@ -1192,12 +1175,13 @@ public function postProcess() { * @return bool * true if data exists, false otherwise */ - public static function blockDataExists(&$fields) { + public static function blockDataExists($fields): bool { if (!is_array($fields)) { + CRM_Core_Error::deprecatedWarning('support for invalid values will be dropped'); return FALSE; } - static $skipFields = [ + $dataFields = array_filter(array_diff_key($fields, array_fill_keys([ 'location_type_id', 'is_primary', 'phone_type_id', @@ -1205,34 +1189,8 @@ public static function blockDataExists(&$fields) { 'country_id', 'website_type_id', 'master_id', - ]; - foreach ($fields as $name => $value) { - $skipField = FALSE; - foreach ($skipFields as $skip) { - if (strpos("[$skip]", $name) !== FALSE) { - if ($name == 'phone') { - continue; - } - $skipField = TRUE; - break; - } - } - if ($skipField) { - continue; - } - if (is_array($value)) { - if (self::blockDataExists($value)) { - return TRUE; - } - } - else { - if (!empty($value)) { - return TRUE; - } - } - } - - return FALSE; + ], TRUE))); + return !empty($dataFields); } /** @@ -1321,7 +1279,7 @@ public function getTemplateFileName() { if ($this->_contactSubType) { $templateFile = "CRM/Contact/Form/Edit/SubType/{$this->_contactSubType}.tpl"; $template = CRM_Core_Form::getTemplate(); - if ($template->template_exists($templateFile)) { + if ($template->templateExists($templateFile)) { return $templateFile; } } @@ -1513,4 +1471,191 @@ public function getContactID(): ?int { return $this->_contactId ? (int) $this->_contactId : NULL; } + private function buildLocationBlock(string $name): void { + if ($name === 'Address') { + CRM_Contact_Form_Edit_Address::buildQuickForm($this); + return; + } + if ($name === 'Phone') { + CRM_Contact_Form_Edit_Phone::buildQuickForm($this); + return; + } + if ($name === 'IM') { + CRM_Contact_Form_Edit_IM::buildQuickForm($this); + return; + } + if ($name === 'Website') { + CRM_Contact_Form_Edit_Website::buildQuickForm($this); + return; + } + if ($name === 'IM') { + CRM_Contact_Form_Edit_IM::buildQuickForm($this); + return; + } + if ($name === 'OpenID') { + CRM_Contact_Form_Edit_OpenID::buildQuickForm($this); + return; + } + CRM_Core_Error::deprecatedWarning('unused?'); + $this->buildBlock($name); + } + + /** + * @param $name + * + * @return void + */ + private function buildBlock(string $name): void { + if ($name === 'TagsAndGroups') { + CRM_Core_Error::deprecatedWarning('unused?'); + CRM_Contact_Form_Edit_TagsAndGroups::buildQuickForm($this); + return; + } + if ($name === 'CustomData') { + $this->buildCustomData(); + return; + } + if ($name === 'CommunicationPreferences') { + CRM_Contact_Form_Edit_CommunicationPreferences::buildQuickForm($this); + return; + } + if ($name === 'Demographics') { + CRM_Contact_Form_Edit_Demographics::buildQuickForm($this); + return; + } + if ($name === 'Notes') { + CRM_Contact_Form_Edit_Notes::buildQuickForm($this); + return; + } + CRM_Core_Error::deprecatedWarning('unused?'); + // Ideally nothing here would be called but it's possible it could be injected from + // outside of core. For core purposed we can be sure no core forms are called here + // and any extensions doing magic are buyer-beware. + $className = 'CRM_Contact_Form_Edit_' . $name; + $className::buildQuickForm($this); + } + + /** + * Build the form object elements for CustomData object. + * + * @throws \CRM_Core_Exception + */ + private function buildCustomData(): void { + $customDataType = CRM_Utils_Request::retrieve('type', 'String'); + // if customDataType is present it implies it is a new contact - ie + // the person has selected 'New Individual' and the type 'Individual' is in the url. + if ($customDataType) { + $this->assign('addBlock', TRUE); + $this->assign('blockName', 'CustomData'); + } + $isAddContact = $this->getAction() === CRM_Core_Action::ADD; + $this->assign('cgCount', 1); + $type = CRM_Utils_Request::retrieve('type', 'String') ?: $this->_contactType; + $subType = $this->isSubmitted() ? ($this->_submitValues['contact_sub_type'] ?? []) : $this->getContactValue('contact_sub_type') ?? $this->_contactSubType; + $groupTree = CRM_Core_BAO_CustomGroup::getTree($type, + NULL, + $this->getContactID(), + NULL, + $subType + ); + + if (!empty($groupTree)) { + $this->_customValueCount = CRM_Core_BAO_CustomGroup::buildCustomDataView($this, $groupTree, TRUE, NULL, NULL, NULL, $this->getContactID()); + } + foreach ($groupTree as $customGroup) { + foreach ($customGroup['fields'] ?? [] as $customField) { + if ($customField['data_type'] === 'File') { + $this->registerFileField(["custom_{$customField['id']}_-" . ($isAddContact ? '' : 1)]); + } + } + } + + // we should use simplified formatted groupTree + $groupTree = CRM_Core_BAO_CustomGroup::formatGroupTree($groupTree, 1); + + if (!$customDataType) { + // Probably this does not need to be wrapped in an IF. + $this->assign('customValueCount', $this->_customValueCount); + } + if ($customDataType) { + // Not too sure why this makes sense for new contacts only. + $this->assign('groupTree', $groupTree); + } + $customValueCount = $this->_submitValues['hidden_custom_group_count'] ?? NULL; + if (is_array($customValueCount)) { + if (array_key_exists(0, $customValueCount)) { + unset($customValueCount[0]); + } + $this->_customValueCount = $customValueCount; + $this->assign('customValueCount', $customValueCount); + } + + $this->addElement('hidden', 'hidden_custom', 1); + $this->addElement('hidden', "hidden_custom_group_count[]", ($this->getAction() !== CRM_Core_Action::ADD)); + CRM_Core_BAO_CustomGroup::buildQuickForm($this, $groupTree); + CRM_Core_BAO_CustomGroup::setDefaults($groupTree, $this->_values, FALSE, FALSE, $this->getAction()); + + //build custom data. + if (!empty($_POST["hidden_custom"]) && !empty($_POST['contact_sub_type'])) { + $contactSubType = $_POST['contact_sub_type']; + } + else { + $contactSubType = $this->_values['contact_sub_type'] ?? NULL; + } + $this->assign('contactType', $this->_contactType); + $this->assign('contactSubType', $contactSubType); + } + + /** + * @return void + */ + private function buildContactTypeSpecificFields(): void { + switch ($this->_contactType) { + case 'Individual': + CRM_Contact_Form_Edit_Individual::buildQuickForm($this); + return; + + case 'Organization': + CRM_Contact_Form_Edit_Organization::buildQuickForm($this); + return; + + case 'Household': + CRM_Contact_Form_Edit_Household::buildQuickForm($this); + return; + + default: + CRM_Core_Error::deprecatedWarning('unreachable?'); + $className = 'CRM_Contact_Form_Edit_' . $this->_contactType; + $className::buildQuickForm($this); + } + } + + /** + * @param array $defaults + * + * @param string $name + * + * @return void + */ + private function setBlockDefaults(array &$defaults, string $name): void { + if ($name === 'TagsAndGroups') { + CRM_Contact_Form_Edit_TagsAndGroups::setDefaultValues($this, $defaults); + return; + } + if ($name === 'CustomData') { + return; + } + if ($name === 'CommunicationPreferences') { + CRM_Contact_Form_Edit_CommunicationPreferences::setDefaultValues($this, $defaults); + return; + } + if ($name === 'Demographics') { + CRM_Contact_Form_Edit_Demographics::setDefaultValues($this, $defaults); + return; + } + CRM_Core_Error::deprecatedWarning('unused?'); + $className = 'CRM_Contact_Form_Edit_' . $name; + $className::setDefaultValues($this, $defaults); + } + } diff --git a/www/modules/civicrm/CRM/Contact/Form/CustomData.php b/www/modules/civicrm/CRM/Contact/Form/CustomData.php index 9769ab9fa..330a04332 100644 --- a/www/modules/civicrm/CRM/Contact/Form/CustomData.php +++ b/www/modules/civicrm/CRM/Contact/Form/CustomData.php @@ -84,23 +84,18 @@ class CRM_Contact_Form_CustomData extends CRM_Core_Form { */ public function preProcess() { $this->_cdType = $_GET['type'] ?? NULL; - $this->assign('cdType', FALSE); $this->_multiRecordDisplay = CRM_Utils_Request::retrieve('multiRecordDisplay', 'String', $this); - if ($this->_cdType || $this->_multiRecordDisplay == 'single') { - if ($this->_cdType) { - $this->assign('cdType', TRUE); - } + $isBuildForm = $this->_cdType && $this->_multiRecordDisplay; + $this->assign('cdType', (bool) $this->_cdType); + if ($isBuildForm) { // NOTE : group id is not stored in session from within CRM_Custom_Form_CustomData::preProcess func // this is due to some condition inside it which restricts it from saving in session // so doing this for multi record edit action $entityId = CRM_Utils_Request::retrieve('entityID', 'Positive', $this); - if (!empty($entityId)) { - $subType = CRM_Contact_BAO_Contact::getContactSubType($entityId, ','); - } - CRM_Custom_Form_CustomData::preProcess($this, NULL, $subType, NULL, CRM_Utils_Request::retrieve('type', 'String', $this), $entityId); + $this->preProcessCustomData(NULL, CRM_Utils_Request::retrieve('type', 'String', $this), $entityId); if ($this->_multiRecordDisplay) { $this->_groupID = CRM_Utils_Request::retrieve('groupID', 'Positive', $this); - $this->_tableID = $this->_entityId; + $this->_tableID = $entityId; $this->_contactType = CRM_Contact_BAO_Contact::getContactType($this->_tableID); $mode = CRM_Utils_Request::retrieve('mode', 'String', $this); $hasReachedMax = CRM_Core_BAO_CustomGroup::hasReachedMaxLimit($this->_groupID, $this->_tableID); @@ -126,10 +121,14 @@ public function preProcess() { if (!empty($_POST['hidden_custom'])) { $this->assign('postedInfo', TRUE); + CRM_Core_Error::deprecatedWarning("I'm kinda confused - how did we get here?"); } } return; } + else { + CRM_Core_Error::deprecatedWarning("I'm so confused - how did we get here?"); + } $this->_groupID = CRM_Utils_Request::retrieve('groupID', 'Positive', $this, TRUE); $this->_tableID = CRM_Utils_Request::retrieve('tableId', 'Positive', $this, TRUE); @@ -143,11 +142,111 @@ public function preProcess() { // when custom data is included in this page if (!empty($_POST['hidden_custom'])) { for ($i = 1; $i <= $_POST['hidden_custom_group_count'][$this->_groupID]; $i++) { - CRM_Custom_Form_CustomData::preProcess($this, NULL, $this->_contactSubType, $i, $this->_contactType, $this->_tableID); - CRM_Custom_Form_CustomData::buildQuickForm($this); - CRM_Custom_Form_CustomData::setDefaultValues($this); + $this->preProcessCustomData($i, $this->_contactType, $this->_tableID); + $this->addElement('hidden', 'hidden_custom', 1); + $this->addElement('hidden', "hidden_custom_group_count[{$this->_groupID}]", $this->_groupCount); + CRM_Core_BAO_CustomGroup::buildQuickForm($this, $this->_groupTree); + } + } + } + + /** + * Previously shared function + * + * @param null|int $groupCount + * @param null $type + * @param null|int $entityID + * + * @throws \CRM_Core_Exception + * @deprecated see https://github.com/civicrm/civicrm-core/pull/29241 for preferred approach - basically + * 1) at the tpl layer use CRM/common/customDataBlock.tpl + * 2) to make the fields available for postProcess + * if ($this->isSubmitted()) { + * $this->addCustomDataFieldsToForm('FinancialAccount'); + * } + * 3) pass getSubmittedValues() to CRM_Core_BAO_CustomField::postProcess($this->getSubmittedValues(), $this->_id, 'FinancialAccount'); + * to ensure any money or number fields are handled for localisation + */ + private function preProcessCustomData($groupCount = NULL, $type = NULL, $entityID = NULL) { + $form = $this; + + $extendsEntityColumn = CRM_Utils_Request::retrieve('subName', 'String', $form); + if ($extendsEntityColumn === 'null') { + // Is this reachable? + $extendsEntityColumn = NULL; + } + + if ($groupCount) { + $form->_groupCount = $groupCount; + } + else { + $form->_groupCount = CRM_Utils_Request::retrieve('cgcount', 'Positive', $form); + } + + $form->assign('cgCount', $form->_groupCount); + + //carry qf key, since this form is not inhereting core form. + if ($qfKey = CRM_Utils_Request::retrieve('qfKey', 'String')) { + $form->assign('qfKey', $qfKey); + } + + if ($entityID) { + $form->_entityId = $entityID; + } + else { + $form->_entityId = CRM_Utils_Request::retrieve('entityID', 'Positive', $form); + } + + $typeCheck = CRM_Utils_Request::retrieve('type', 'String'); + $urlGroupId = CRM_Utils_Request::retrieve('groupID', 'Positive'); + if (isset($typeCheck) && $urlGroupId) { + $form->_groupID = $urlGroupId; + } + else { + $form->_groupID = CRM_Utils_Request::retrieve('groupID', 'Positive', $form); + } + + $gid = (isset($form->_groupID)) ? $form->_groupID : NULL; + + $singleRecord = NULL; + if (!empty($form->_groupCount) && !empty($form->_multiRecordDisplay) && $form->_multiRecordDisplay == 'single') { + $singleRecord = $form->_groupCount; + } + $mode = CRM_Utils_Request::retrieve('mode', 'String', $form); + // when a new record is being added for multivalued custom fields. + if (isset($form->_groupCount) && $form->_groupCount == 0 && $mode == 'add' && + !empty($form->_multiRecordDisplay) && $form->_multiRecordDisplay == 'single') { + $singleRecord = 'new'; + } + + $groupTree = CRM_Core_BAO_CustomGroup::getTree($type, + NULL, + $form->_entityId, + $gid, + CRM_Contact_BAO_Contact::getContactSubType($entityID), + $extendsEntityColumn, + TRUE, + NULL, + FALSE, + CRM_Core_Permission::EDIT, + $singleRecord + ); + + if (property_exists($form, '_customValueCount') && !empty($groupTree)) { + $form->_customValueCount = CRM_Core_BAO_CustomGroup::buildCustomDataView($form, $groupTree, TRUE, NULL, NULL, NULL, $form->_entityId); + } + // we should use simplified formatted groupTree + $groupTree = CRM_Core_BAO_CustomGroup::formatGroupTree($groupTree, $form->_groupCount, $form); + + if (isset($form->_groupTree) && is_array($form->_groupTree)) { + $keys = array_keys($groupTree); + foreach ($keys as $key) { + $form->_groupTree[$key] = $groupTree[$key]; } } + else { + $form->_groupTree = $groupTree; + } } /** @@ -182,7 +281,10 @@ public function buildQuickForm() { ]); } } - return CRM_Custom_Form_CustomData::buildQuickForm($this); + $this->addElement('hidden', 'hidden_custom', 1); + $this->addElement('hidden', "hidden_custom_group_count[{$this->_groupID}]", $this->_groupCount); + CRM_Core_BAO_CustomGroup::buildQuickForm($this, $this->_groupTree); + return; } //need to assign custom data type and subtype to the template @@ -239,7 +341,8 @@ public function setDefaultValues() { } } else { - $customDefaultValue = CRM_Custom_Form_CustomData::setDefaultValues($this); + $customDefaultValue = []; + CRM_Core_BAO_CustomGroup::setDefaults($this->_groupTree, $customDefaultValue, FALSE, FALSE, $this->get('action')); } return $customDefaultValue; } @@ -274,7 +377,7 @@ public function postProcess() { $this->_tableID, $this->_entityType ); - $table = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $this->_groupID, 'table_name'); + $table = CRM_Core_BAO_CustomGroup::getGroup(['id' => $this->_groupID])['table_name']; $cgcount = CRM_Core_BAO_CustomGroup::customGroupDataExistsForEntity($this->_tableID, $table, TRUE); $cgcount += 1; $buttonName = $this->controller->getButtonName(); diff --git a/www/modules/civicrm/CRM/Contact/Form/Domain.php b/www/modules/civicrm/CRM/Contact/Form/Domain.php index 964705eb8..f2fc2580a 100644 --- a/www/modules/civicrm/CRM/Contact/Form/Domain.php +++ b/www/modules/civicrm/CRM/Contact/Form/Domain.php @@ -120,7 +120,14 @@ public function buildQuickForm(): void { //build location blocks. $this->assign('addressSequence', CRM_Core_BAO_Address::addressSequence()); CRM_Contact_Form_Edit_Address::buildQuickForm($this, 1); - CRM_Contact_Form_Edit_Email::buildQuickForm($this, 1); + + //Email box + $this->addField("email[1][email]", [ + 'entity' => 'email', + 'aria-label' => ts('Email 1'), + 'label' => ts('Email 1'), + ]); + $this->addRule("email[1][email]", ts('Email is not valid.'), 'email'); CRM_Contact_Form_Edit_Phone::buildQuickForm($this, 1); $this->addButtons([ diff --git a/www/modules/civicrm/CRM/Contact/Form/Edit/Address.php b/www/modules/civicrm/CRM/Contact/Form/Edit/Address.php index f7cbe1688..8411c7d70 100644 --- a/www/modules/civicrm/CRM/Contact/Form/Edit/Address.php +++ b/www/modules/civicrm/CRM/Contact/Form/Edit/Address.php @@ -176,42 +176,8 @@ public static function buildQuickForm(&$form, $addressBlockCount = NULL, $sharin public static function formRule($fields, $files = [], $self = NULL) { $errors = []; - $customDataRequiredFields = []; - if ($self && property_exists($self, '_addressRequireOmission')) { - $customDataRequiredFields = explode(',', $self->_addressRequireOmission); - } - if (!empty($fields['address']) && is_array($fields['address'])) { foreach ($fields['address'] as $instance => $addressValues) { - - if (CRM_Utils_System::isNull($addressValues)) { - // DETACH 'required' form rule error to - // custom data only if address data not exists upon submission - if (!empty($customDataRequiredFields)) { - foreach ($customDataRequiredFields as $customElementName) { - $elementName = "address[$instance][$customElementName]"; - if ($self->getElementError($elementName)) { - // set element error to none - $self->setElementError($elementName, NULL); - } - } - } - continue; - } - - // DETACH 'required' form rule error to - // custom data if address data not exists upon submission - // or if master address is selected - if (!empty($customDataRequiredFields) && (!CRM_Core_BAO_Address::dataExists($addressValues) || !empty($addressValues['master_id']))) { - foreach ($customDataRequiredFields as $customElementName) { - $elementName = "address[$instance][$customElementName]"; - if ($self->getElementError($elementName)) { - // set element error to none - $self->setElementError($elementName, NULL); - } - } - } - if (!empty($addressValues['use_shared_address']) && empty($addressValues['master_id'])) { $errors["address[$instance][use_shared_address]"] = ts('Please select valid shared contact or a contact with valid address.'); } @@ -226,7 +192,7 @@ public static function formRule($fields, $files = [], $self = NULL) { * * @param array $defaults * Defaults associated array. - * @param CRM_Core_Form $form + * @param \CRM_Contact_Form_Contact|\CRM_Contact_Form_Edit_Address $form * Form object. */ public static function setDefaultValues(&$defaults, &$form) { @@ -344,36 +310,6 @@ public static function setDefaultValues(&$defaults, &$form) { } } - /** - * Store required custom data info. - * - * @param CRM_Core_Form $form - * @param array $groupTree - */ - public static function storeRequiredCustomDataInfo(&$form, $groupTree) { - if (in_array(CRM_Utils_System::getClassName($form), ['CRM_Contact_Form_Contact', 'CRM_Contact_Form_Inline_Address'])) { - $requireOmission = ''; - foreach ($groupTree as $csId => $csVal) { - // only process Address entity fields - if ($csVal['extends'] !== 'Address') { - continue; - } - - foreach ($csVal['fields'] as $cdId => $cdVal) { - if (!empty($cdVal['is_required'])) { - $elementName = $cdVal['element_name']; - if (in_array($elementName, $form->_required)) { - // store the omitted rule for a element, to be used later on - $requireOmission .= $cdVal['element_custom_name'] . ','; - } - } - } - } - - $form->_addressRequireOmission = rtrim($requireOmission, ','); - } - } - /** * Add custom data to the form. * @@ -385,19 +321,20 @@ public static function storeRequiredCustomDataInfo(&$form, $groupTree) { */ protected static function addCustomDataToForm(&$form, $entityId, $blockId) { $groupTree = CRM_Core_BAO_CustomGroup::getTree('Address', NULL, $entityId); - if (isset($groupTree) && is_array($groupTree)) { // use simplified formatted groupTree $groupTree = CRM_Core_BAO_CustomGroup::formatGroupTree($groupTree, 1, $form); - + $enforceRequiredFields = self::enforceRequired($form); // make sure custom fields are added /w element-name in the format - 'address[$blockId][custom-X]' foreach ($groupTree as $id => $group) { - foreach ($group['fields'] as $fldId => $field) { - $groupTree[$id]['fields'][$fldId]['element_custom_name'] = $field['element_name']; - $groupTree[$id]['fields'][$fldId]['element_name'] = "address[$blockId][{$field['element_name']}]"; + foreach ($group['fields'] as $fieldID => $field) { + $groupTree[$id]['fields'][$fieldID]['element_custom_name'] = $field['element_name']; + $groupTree[$id]['fields'][$fieldID]['element_name'] = "address[$blockId][{$field['element_name']}]"; + if (!$enforceRequiredFields) { + $groupTree[$id]['fields'][$fieldID]['is_required'] = 0; + } } } - $defaults = []; CRM_Core_BAO_CustomGroup::setDefaults($groupTree, $defaults); @@ -420,11 +357,6 @@ protected static function addCustomDataToForm(&$form, $entityId, $blockId) { // And we can't set it to 'address_' because we want to set it in a slightly different format. CRM_Core_BAO_CustomGroup::buildQuickForm($form, $groupTree, FALSE, 'dnc_'); - // during contact editing : if no address is filled - // required custom data must not produce 'required' form rule error - // more handling done in formRule func - CRM_Contact_Form_Edit_Address::storeRequiredCustomDataInfo($form, $groupTree); - $tplGroupTree = CRM_Core_Smarty::singleton() ->getTemplateVars('address_groupTree'); $tplGroupTree = empty($tplGroupTree) ? [] : $tplGroupTree; @@ -436,4 +368,26 @@ protected static function addCustomDataToForm(&$form, $entityId, $blockId) { // address custom data processing ends .. } + /** + * Should required fields be enforced. + * + * This would be FALSE if there were no other address fields present. + * + * @param \CRM_Core_Form $form + * + * @return bool + */ + private static function enforceRequired(CRM_Core_Form $form): bool { + if ($form->isSubmitted()) { + $addresses = (array) $form->getSubmittedValue('address'); + foreach ($addresses as $address) { + if (!empty($address['master_id']) || CRM_Core_BAO_Address::dataExists($address)) { + return TRUE; + } + } + return FALSE; + } + return TRUE; + } + } diff --git a/www/modules/civicrm/CRM/Contact/Form/Edit/CommunicationPreferences.php b/www/modules/civicrm/CRM/Contact/Form/Edit/CommunicationPreferences.php index 2a6b1e2c7..d3655df2f 100644 --- a/www/modules/civicrm/CRM/Contact/Form/Edit/CommunicationPreferences.php +++ b/www/modules/civicrm/CRM/Contact/Form/Edit/CommunicationPreferences.php @@ -186,7 +186,7 @@ public static function setDefaultValues(&$form, &$defaults) { else { foreach (CRM_Contact_BAO_Contact::$_greetingTypes as $greeting) { $name = "{$greeting}_display"; - $form->assign($name, CRM_Utils_Array::value($name, $defaults)); + $form->assign($name, $defaults[$name] ?? NULL); } } } diff --git a/www/modules/civicrm/CRM/Contact/Form/Edit/CustomData.php b/www/modules/civicrm/CRM/Contact/Form/Edit/CustomData.php index 9593b3927..66d6922f8 100644 --- a/www/modules/civicrm/CRM/Contact/Form/Edit/CustomData.php +++ b/www/modules/civicrm/CRM/Contact/Form/Edit/CustomData.php @@ -16,18 +16,23 @@ */ /** - * Form helper class for an Demographics object. + * Form helper class for an Demographics object (ahem). + * + * @deprecated since 5.73 will be removed around 5.85 */ class CRM_Contact_Form_Edit_CustomData { /** * Build all the data structures needed to build the form. * + * @deprecated since 5.73 will be removed around 5.85 + * * @param CRM_Core_Form $form * * @throws \CRM_Core_Exception */ public static function preProcess(&$form) { + CRM_Core_Error::deprecatedFunctionWarning('maybe take a copy?'); $customDataType = CRM_Utils_Request::retrieve('type', 'String'); if ($customDataType) { @@ -46,10 +51,13 @@ public static function preProcess(&$form) { /** * Build the form object elements for CustomData object. * + * @deprecated since 5.73 will be removed around 5.85 + * * @param CRM_Core_Form $form * Reference to the form object. */ public static function buildQuickForm(&$form) { + CRM_Core_Error::deprecatedFunctionWarning('take a copy?'); $customValueCount = $form->_submitValues['hidden_custom_group_count'] ?? NULL; if (is_array($customValueCount)) { if (array_key_exists(0, $customValueCount)) { @@ -58,7 +66,9 @@ public static function buildQuickForm(&$form) { $form->_customValueCount = $customValueCount; $form->assign('customValueCount', $customValueCount); } - CRM_Custom_Form_CustomData::buildQuickForm($form); + $form->addElement('hidden', 'hidden_custom', 1); + $form->addElement('hidden', "hidden_custom_group_count[{$form->_groupID}]", $form->_groupCount); + CRM_Core_BAO_CustomGroup::buildQuickForm($form, $form->_groupTree); //build custom data. $contactSubType = NULL; @@ -76,12 +86,15 @@ public static function buildQuickForm(&$form) { * Set default values for the form. Note that in edit/view mode * the default values are retrieved from the database * + * @deprecated since 5.73 will be removed around 5.85 * * @param CRM_Core_Form $form * @param array $defaults */ public static function setDefaultValues(&$form, &$defaults) { - $defaults += CRM_Custom_Form_CustomData::setDefaultValues($form); + CRM_Core_Error::deprecatedFunctionWarning('take a copy?'); + CRM_Core_BAO_CustomGroup::setDefaults($form->_groupTree, $defaults, FALSE, FALSE, $form->get('action')); + return $defaults; } } diff --git a/www/modules/civicrm/CRM/Contact/Form/Inline.php b/www/modules/civicrm/CRM/Contact/Form/Inline.php index 253f66731..b55bfe787 100644 --- a/www/modules/civicrm/CRM/Contact/Form/Inline.php +++ b/www/modules/civicrm/CRM/Contact/Form/Inline.php @@ -23,6 +23,8 @@ abstract class CRM_Contact_Form_Inline extends CRM_Core_Form { /** * Id of the contact that is being edited * @var int + * + * @internal - use getContactID() */ public $_contactId; @@ -56,12 +58,11 @@ public function getDefaultEntity() { * Common preprocess: fetch contact ID and contact type */ public function preProcess() { - $this->_contactId = CRM_Utils_Request::retrieve('cid', 'Positive', $this, TRUE); - $this->assign('contactId', $this->_contactId); + $this->assign('contactId', $this->getContactID()); // get contact type and subtype if (empty($this->_contactType)) { - $contactTypeInfo = CRM_Contact_BAO_Contact::getContactTypes($this->_contactId); + $contactTypeInfo = CRM_Contact_BAO_Contact::getContactTypes($this->getContactID()); $this->_contactType = $contactTypeInfo[0]; // check if subtype is set @@ -77,11 +78,28 @@ public function preProcess() { $this->setAction(CRM_Core_Action::UPDATE); } + /** + * Get the contact ID. + * + * Override this for more complex retrieval as required by the form. + * + * @return int|null + * + * @noinspection PhpUnhandledExceptionInspection + * @noinspection PhpDocMissingThrowsInspection + */ + public function getContactID(): int { + if (!isset($this->_contactId)) { + $this->_contactId = (int) CRM_Utils_Request::retrieve('cid', 'Positive', $this, TRUE); + } + return $this->_contactId; + } + /** * Common form elements. */ public function buildQuickForm() { - CRM_Contact_Form_Inline_Lock::buildQuickForm($this, $this->_contactId); + CRM_Contact_Form_Inline_Lock::buildQuickForm($this, $this->getContactID()); $buttons = [ [ @@ -111,10 +129,8 @@ public function cancelAction() { * @return array */ public function setDefaultValues() { - $defaults = $params = []; - $params['id'] = $this->_contactId; - - CRM_Contact_BAO_Contact::getValues($params, $defaults); + $defaults = []; + CRM_Contact_BAO_Contact::getValues(['id' => $this->getContactID()], $defaults); return $defaults; } @@ -123,9 +139,9 @@ public function setDefaultValues() { * Add entry to log table. */ protected function log() { - CRM_Core_BAO_Log::register($this->_contactId, + CRM_Core_BAO_Log::register($this->getContactID(), 'civicrm_contact', - $this->_contactId + $this->getContactID() ); } @@ -136,9 +152,9 @@ protected function log() { */ protected function response() { $this->ajaxResponse = array_merge( - self::renderFooter($this->_contactId), + self::renderFooter($this->getContactID()), $this->ajaxResponse, - CRM_Contact_Form_Inline_Lock::getResponse($this->_contactId) + CRM_Contact_Form_Inline_Lock::getResponse($this->getContactID()) ); // Note: Post hooks will be called by CRM_Core_Form::mainProcess } diff --git a/www/modules/civicrm/CRM/Contact/Form/Inline/CommunicationPreferences.php b/www/modules/civicrm/CRM/Contact/Form/Inline/CommunicationPreferences.php index ec2b48dae..2b3785721 100644 --- a/www/modules/civicrm/CRM/Contact/Form/Inline/CommunicationPreferences.php +++ b/www/modules/civicrm/CRM/Contact/Form/Inline/CommunicationPreferences.php @@ -51,7 +51,7 @@ public function setDefaultValues() { foreach (CRM_Contact_BAO_Contact::$_greetingTypes as $greeting) { $name = "{$greeting}_display"; - $this->assign($name, CRM_Utils_Array::value($name, $defaults)); + $this->assign($name, $defaults[$name] ?? NULL); } return $defaults; } diff --git a/www/modules/civicrm/CRM/Contact/Form/Inline/CustomData.php b/www/modules/civicrm/CRM/Contact/Form/Inline/CustomData.php index 96247fa48..ce3f6ef2e 100644 --- a/www/modules/civicrm/CRM/Contact/Form/Inline/CustomData.php +++ b/www/modules/civicrm/CRM/Contact/Form/Inline/CustomData.php @@ -35,26 +35,66 @@ class CRM_Contact_Form_Inline_CustomData extends CRM_Contact_Form_Inline { protected $_entityType; /** - * Call preprocess. + * Build the form object elements for custom data. + * + * @throws \CRM_Core_Exception */ - public function preProcess() { - parent::preProcess(); - + public function buildQuickForm(): void { + parent::buildQuickForm(); $this->_groupID = CRM_Utils_Request::retrieve('groupID', 'Positive', $this, TRUE, NULL); $this->assign('customGroupId', $this->_groupID); - $customRecId = CRM_Utils_Request::retrieve('customRecId', 'Positive', $this, FALSE, 1); - $cgcount = CRM_Utils_Request::retrieve('cgcount', 'Positive', $this, FALSE, 1); - $subType = CRM_Contact_BAO_Contact::getContactSubType($this->_contactId, ','); - CRM_Custom_Form_CustomData::preProcess($this, NULL, $subType, $cgcount, - $this->_contactType, $this->_contactId); - } + $type = $this->_contactType; + $groupCount = CRM_Utils_Request::retrieve('cgcount', 'Positive', $this, FALSE, 1); + $this->assign('cgCount', $groupCount); - /** - * Build the form object elements for custom data. - */ - public function buildQuickForm() { - parent::buildQuickForm(); - CRM_Custom_Form_CustomData::buildQuickForm($this); + $extendsEntityColumn = CRM_Utils_Request::retrieve('subName', 'String', $this); + if ($extendsEntityColumn === 'null') { + // Is this reachable? + $extendsEntityColumn = NULL; + } + + //carry qf key, since this form is not inheriting core form. + if ($qfKey = CRM_Utils_Request::retrieve('qfKey', 'String')) { + $this->assign('qfKey', $qfKey); + } + + $typeCheck = CRM_Utils_Request::retrieve('type', 'String'); + $urlGroupId = CRM_Utils_Request::retrieve('groupID', 'Positive'); + if (isset($typeCheck) && $urlGroupId) { + $this->_groupID = $urlGroupId; + } + else { + $this->_groupID = CRM_Utils_Request::retrieve('groupID', 'Positive', $this); + } + + $gid = (isset($this->_groupID)) ? $this->_groupID : NULL; + + $groupTree = CRM_Core_BAO_CustomGroup::getTree($type, + NULL, + $this->getContactID(), + $gid, + CRM_Contact_BAO_Contact::getContactSubType($this->getContactID()), + $extendsEntityColumn + ); + + if (property_exists($this, '_customValueCount') && !empty($groupTree)) { + $this->_customValueCount = CRM_Core_BAO_CustomGroup::buildCustomDataView($this, $groupTree, TRUE, NULL, NULL, NULL, $this->getContactID()); + } + // we should use simplified formatted groupTree + $groupTree = CRM_Core_BAO_CustomGroup::formatGroupTree($groupTree, $groupCount, $this); + + if (isset($this->_groupTree) && is_array($this->_groupTree)) { + $keys = array_keys($groupTree); + foreach ($keys as $key) { + $this->_groupTree[$key] = $groupTree[$key]; + } + } + else { + $this->_groupTree = $groupTree; + } + $this->addElement('hidden', 'hidden_custom', 1); + $this->addElement('hidden', "hidden_custom_group_count[{$this->_groupID}]", CRM_Utils_Request::retrieve('cgcount', 'Positive', $this, FALSE, 1)); + CRM_Core_BAO_CustomGroup::buildQuickForm($this, $this->_groupTree); } /** @@ -62,20 +102,22 @@ public function buildQuickForm() { * * @return array */ - public function setDefaultValues() { - return CRM_Custom_Form_CustomData::setDefaultValues($this); + public function setDefaultValues(): array { + $defaults = []; + CRM_Core_BAO_CustomGroup::setDefaults($this->_groupTree, $defaults, FALSE, FALSE, $this->get('action')); + return $defaults; } /** * Process the form. */ - public function postProcess() { + public function postProcess(): void { // Process / save custom data // Get the form values and groupTree $params = $this->getSubmittedValues(); CRM_Core_BAO_CustomValueTable::postProcess($params, 'civicrm_contact', - $this->_contactId, + $this->getContactID(), $this->_entityType ); diff --git a/www/modules/civicrm/CRM/Contact/Form/Merge.php b/www/modules/civicrm/CRM/Contact/Form/Merge.php index 95c12ffaf..79a2d66b8 100644 --- a/www/modules/civicrm/CRM/Contact/Form/Merge.php +++ b/www/modules/civicrm/CRM/Contact/Form/Merge.php @@ -104,11 +104,7 @@ public function preProcess() { } $cacheKey = CRM_Dedupe_Merger::getMergeCacheKeyString($this->_rgid, $gid, json_decode($this->criteria, TRUE), TRUE, $this->limit); - - $join = CRM_Dedupe_Merger::getJoinOnDedupeTable(); - $where = "de.id IS NULL"; - - $pos = CRM_Core_BAO_PrevNextCache::getPositions($cacheKey, $this->_cid, $this->_oid, $this->_mergeId, $join, $where, $flip); + $pos = CRM_Core_BAO_PrevNextCache::getPositions($cacheKey, $flip ? $this->_oid : $this->_cid, $flip ? $this->_cid : $this->_oid, $this->_mergeId); // get user info of main contact. $config = CRM_Core_Config::singleton(); @@ -177,11 +173,11 @@ public function preProcess() { $this->assign('mainLocBlock', json_encode($rowsElementsAndInfo['main_details']['location_blocks'])); $this->assign('locationBlockInfo', json_encode(CRM_Dedupe_Merger::getLocationBlockInfo())); - $this->assign('mainContactTypeIcon', CRM_Contact_BAO_Contact_Utils::getImage($contacts[$this->_cid]['contact_sub_type'] ? $contacts[$this->_cid]['contact_sub_type'] : $contacts[$this->_cid]['contact_type'], + $this->assign('mainContactTypeIcon', CRM_Contact_BAO_Contact_Utils::getImage($contacts[$this->_cid]['contact_sub_type'] ?: $contacts[$this->_cid]['contact_type'], FALSE, $this->_cid )); - $this->assign('otherContactTypeIcon', CRM_Contact_BAO_Contact_Utils::getImage($contacts[$this->_oid]['contact_sub_type'] ? $contacts[$this->_oid]['contact_sub_type'] : $contacts[$this->_oid]['contact_type'], + $this->assign('otherContactTypeIcon', CRM_Contact_BAO_Contact_Utils::getImage($contacts[$this->_oid]['contact_sub_type'] ?: $contacts[$this->_oid]['contact_type'], FALSE, $this->_oid )); @@ -339,10 +335,7 @@ public function postProcess() { elseif ($this->next && $this->_mergeId && empty($formValues['_qf_Merge_done'])) { $cacheKey = CRM_Dedupe_Merger::getMergeCacheKeyString($this->_rgid, $this->_gid, json_decode($this->criteria, TRUE), TRUE, $this->limit); - $join = CRM_Dedupe_Merger::getJoinOnDedupeTable(); - $where = "de.id IS NULL"; - - $pos = CRM_Core_BAO_PrevNextCache::getPositions($cacheKey, NULL, NULL, $this->_mergeId, $join, $where); + $pos = CRM_Core_BAO_PrevNextCache::getPositions($cacheKey, NULL, NULL, $this->_mergeId); if (!empty($pos) && $pos['next']['id1'] && diff --git a/www/modules/civicrm/CRM/Contact/Form/Relationship.php b/www/modules/civicrm/CRM/Contact/Form/Relationship.php index 7001592c6..d1cd0942b 100644 --- a/www/modules/civicrm/CRM/Contact/Form/Relationship.php +++ b/www/modules/civicrm/CRM/Contact/Form/Relationship.php @@ -14,6 +14,8 @@ */ class CRM_Contact_Form_Relationship extends CRM_Core_Form { + use CRM_Custom_Form_CustomDataTrait; + /** * The relationship id, used when editing the relationship * @@ -175,8 +177,7 @@ public function preProcess() { $this->_rtype = str_replace($this->_relationshipTypeId . '_', '', $this->_rtypeId); } - //need to assign custom data type and subtype to the template - FIXME: explain why - $this->assign('customDataType', 'Relationship'); + //need to assign custom data subtype to the template for the initial load of custom fields. $this->assign('customDataSubType', $this->_relationshipTypeId); $this->assign('entityID', $this->_relationshipId); @@ -190,14 +191,27 @@ public function preProcess() { } } - // when custom data is included in this page - if (!empty($_POST['hidden_custom'])) { - CRM_Custom_Form_CustomData::preProcess($this, NULL, $this->_relationshipTypeId, 1, 'Relationship', $this->_relationshipId); - CRM_Custom_Form_CustomData::buildQuickForm($this); - CRM_Custom_Form_CustomData::setDefaultValues($this); + if ($this->isSubmitted()) { + // The custom data fields are added to the form by an ajax form. + // However, if they are not present in the element index they will + // not be available from `$this->getSubmittedValue()` in post process. + // We do not have to set defaults or otherwise render - just add to the element index. + $this->addCustomDataFieldsToForm('Relationship', array_filter([ + 'id' => $this->getRelationshipID(), + 'relationship_type_id' => $this->_relationshipTypeId, + ])); } } + /** + * @api supported for use from outside core. + * + * @return int|null + */ + public function getRelationshipID(): ?int { + return $this->_relationshipId; + } + /** * Set default values for the form. */ @@ -381,13 +395,13 @@ public function submit($params) { */ public function postProcess() { // Store the submitted values in an array. - $params = $this->controller->exportValues($this->_name); + $params = $this->getSubmittedValues(); $values = $this->submit($params); if (empty($values)) { return; } - list ($params, $relationshipIds) = $values; + [$params, $relationshipIds] = $values; // if this is called from case view, //create an activity for case role removal.CRM-4480 diff --git a/www/modules/civicrm/CRM/Contact/Form/Search.php b/www/modules/civicrm/CRM/Contact/Form/Search.php index 26ea1f2cb..1bdfcbac9 100644 --- a/www/modules/civicrm/CRM/Contact/Form/Search.php +++ b/www/modules/civicrm/CRM/Contact/Form/Search.php @@ -430,7 +430,7 @@ public function buildQuickForm() { // set the group title $groupValues = ['id' => $this->_groupID, 'title' => $this->_group[$this->_groupID]]; - $this->assign_by_ref('group', $groupValues); + $this->assign('group', $groupValues); // also set ssID if this is a saved search $ssID = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Group', $this->_groupID, 'saved_search_id'); @@ -471,7 +471,7 @@ public function buildQuickForm() { $this->setTitle(ts('Add to Group: %1', [1 => $this->_group[$this->_amtgID]])); // also set the group title and freeze the action task with Add Members to Group $groupValues = ['id' => $this->_amtgID, 'title' => $this->_group[$this->_amtgID]]; - $this->assign_by_ref('group', $groupValues); + $this->assign('group', $groupValues); $this->add('xbutton', $this->_actionButtonName, ts('Add Contacts to %1', [1 => $this->_group[$this->_amtgID]]), [ 'type' => 'submit', @@ -495,7 +495,7 @@ public function buildQuickForm() { $selectedContactIds = array_keys($selectedContactIdsArr[$qfKeyParam]); } - $this->assign_by_ref('selectedContactIds', $selectedContactIds); + $this->assign('selectedContactIds', $selectedContactIds); $rows = $this->get('rows'); diff --git a/www/modules/civicrm/CRM/Contact/Form/Search/Builder.php b/www/modules/civicrm/CRM/Contact/Form/Search/Builder.php index 44108ea0d..2cdf2d64a 100644 --- a/www/modules/civicrm/CRM/Contact/Form/Search/Builder.php +++ b/www/modules/civicrm/CRM/Contact/Form/Search/Builder.php @@ -568,9 +568,11 @@ private function buildMappingForm(&$form, $mappingId, $columnNo, $blockCount) { foreach ($value as $key1 => $value1) { //CRM-2676, replacing the conflict for same custom field name from different custom group. - $customGroupName = CRM_Core_BAO_Mapping::getCustomGroupName($key1); + $customFieldId = CRM_Core_BAO_CustomField::getKeyID($key1); - if ($customGroupName) { + if ($customFieldId) { + $customGroupName = CRM_Core_BAO_CustomField::getField($customFieldId)['custom_group']['title']; + $customGroupName = CRM_Utils_String::ellipsify($customGroupName, 13); $relatedMapperFields[$key][$key1] = $mapperFields[$key][$key1] = $customGroupName . ': ' . $value1['title']; } else { diff --git a/www/modules/civicrm/CRM/Contact/Form/Task/AddToGroup.php b/www/modules/civicrm/CRM/Contact/Form/Task/AddToGroup.php index 4d11df34b..fc8f74610 100644 --- a/www/modules/civicrm/CRM/Contact/Form/Task/AddToGroup.php +++ b/www/modules/civicrm/CRM/Contact/Form/Task/AddToGroup.php @@ -22,6 +22,7 @@ * addition of contacts to groups. */ class CRM_Contact_Form_Task_AddToGroup extends CRM_Contact_Form_Task { + use CRM_Custom_Form_CustomDataTrait; /** * The context that we are working on @@ -52,9 +53,13 @@ public function preProcess() { parent::preProcess(); $this->_context = $this->get('context'); - $this->_id = $this->get('amtgID'); - CRM_Custom_Form_CustomData::preProcess($this, NULL, NULL, 1, 'Group', $this->_id); + $this->assign('entityID', $this->getGroupID()); + } + + public function getGroupID(): ?int { + $this->_id = $this->get('amtgID'); + return $this->_id ? (int) $this->_id : NULL; } /** @@ -117,8 +122,9 @@ public function buildQuickForm() { } else { $this->setTitle(ts('Add Contacts to A Group')); - //build custom data - CRM_Custom_Form_CustomData::buildQuickForm($this); + } + if ($this->isSubmitted()) { + $this->addCustomDataFieldsToForm('Group'); } $this->addDefaultButtons(ts('Add to Group')); @@ -139,7 +145,6 @@ public function setDefaultValues() { } $defaults['group_option'] = 0; - $defaults += CRM_Custom_Form_CustomData::setDefaultValues($this); return $defaults; } @@ -155,7 +160,7 @@ public function addRules() { * * @param array $params * - * @return array + * @return array|true * list of errors to be posted back to the form */ public static function formRule($params) { @@ -174,7 +179,7 @@ public static function formRule($params) { /** * Process the form after the input has been submitted and validated. */ - public function postProcess() { + public function postProcess(): void { $params = $this->controller->exportValues(); $groupOption = $params['group_option'] ?? NULL; if ($groupOption) { @@ -184,7 +189,7 @@ public function postProcess() { $groupParams['visibility'] = 'User and User Admin Only'; $groupParams['group_type'] = array_keys($params['group_type'] ?? []); $groupParams['is_active'] = 1; - $groupParams['custom'] = CRM_Core_BAO_CustomField::postProcess($params, $this->_id, 'Group'); + $groupParams['custom'] = CRM_Core_BAO_CustomField::postProcess($this->getSubmittedValues(), $this->_id, 'Group'); $createdGroup = CRM_Contact_BAO_Group::create($groupParams); $groupID = $createdGroup->id; @@ -196,7 +201,7 @@ public function postProcess() { $groupName = $group[$groupID]; } - list($total, $added, $notAdded) = CRM_Contact_BAO_GroupContact::addContactsToGroup($this->_contactIds, $groupID); + [$total, $added, $notAdded] = CRM_Contact_BAO_GroupContact::addContactsToGroup($this->_contactIds, $groupID); $status = [ ts('%count contact added to group', [ diff --git a/www/modules/civicrm/CRM/Contact/Form/Task/AddToParentClass.php b/www/modules/civicrm/CRM/Contact/Form/Task/AddToParentClass.php index 20300204c..0e169db59 100644 --- a/www/modules/civicrm/CRM/Contact/Form/Task/AddToParentClass.php +++ b/www/modules/civicrm/CRM/Contact/Form/Task/AddToParentClass.php @@ -242,7 +242,7 @@ public function search(&$form, &$params) { $contact_type = '_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this); $cid = CRM_Utils_Request::retrieve('cid', 'Positive', $this, FALSE); - CRM_Contact_Form_Task_SMSCommon::preProcessProvider($this); - - if (!$cid && $this->_context != 'standalone') { + $this->_single = $this->_context !== 'search'; + $this->bounceOnNoActiveProviders(); + if (!$cid && $this->_context !== 'standalone') { parent::preProcess(); } @@ -57,25 +63,7 @@ public function preProcess() { public function buildQuickForm() { //enable form element $this->assign('suppressForm', FALSE); - $this->assign('SMSTask', TRUE); - CRM_Contact_Form_Task_SMSCommon::buildQuickForm($this); - } - - /** - * Process the form after the input has been submitted and validated. - */ - public function postProcess() { - CRM_Contact_Form_Task_SMSCommon::postProcess($this); - } - - /** - * List available tokens for this form. - * - * @return array - */ - public function listTokens() { - $tokens = CRM_Core_SelectValues::contactTokens(); - return $tokens; + $this->buildSmsForm(); } } diff --git a/www/modules/civicrm/CRM/Contact/Form/Task/SMSCommon.php b/www/modules/civicrm/CRM/Contact/Form/Task/SMSCommon.php index a86c49022..6a2af585b 100644 --- a/www/modules/civicrm/CRM/Contact/Form/Task/SMSCommon.php +++ b/www/modules/civicrm/CRM/Contact/Form/Task/SMSCommon.php @@ -17,6 +17,8 @@ /** * This class provides the common functionality for sending sms to one or a group of contact ids. + * + * @deprecated since 5.71 will be removed around 5.77. */ class CRM_Contact_Form_Task_SMSCommon { const RECIEVED_SMS_ACTIVITY_SUBJECT = "SMS Received"; @@ -30,9 +32,12 @@ class CRM_Contact_Form_Task_SMSCommon { /** * Pre process the provider. * + * @deprecated since 5.71 will be removed around 5.77. + * * @param CRM_Core_Form $form */ public static function preProcessProvider(&$form) { + CRM_Core_Error::deprecatedFunctionWarning('no alternative'); $form->_single = FALSE; $className = CRM_Utils_System::getClassName($form); @@ -43,7 +48,7 @@ public static function preProcessProvider(&$form) { $form->_single = TRUE; } - $providersCount = CRM_SMS_BAO_Provider::activeProviderCount(); + $providersCount = CRM_SMS_BAO_SmsProvider::activeProviderCount(); if (!$providersCount) { CRM_Core_Error::statusBounce(ts('There are no SMS providers configured, or no SMS providers are set active')); @@ -68,12 +73,15 @@ public static function preProcessProvider(&$form) { * Build the form object. * * @param CRM_Core_Form $form + * + * @deprecated since 5.71 will be removed around 5.77. */ public static function buildQuickForm(&$form) { + CRM_Core_Error::deprecatedFunctionWarning('no alternative supported for non-core use'); $toArray = []; - $providers = CRM_SMS_BAO_Provider::getProviders(NULL, NULL, TRUE, 'is_default desc'); + $providers = CRM_SMS_BAO_SmsProvider::getProviders(NULL, NULL, TRUE, 'is_default desc'); $providerSelect = []; foreach ($providers as $provider) { @@ -102,7 +110,7 @@ public static function buildQuickForm(&$form) { $form->_contactIds = []; foreach ($allToPhone as $value) { - list($contactId, $phone) = explode('::', $value); + [$contactId, $phone] = explode('::', $value); if ($contactId) { $form->_contactIds[] = $contactId; $form->_toContactPhone[] = $phone; @@ -283,7 +291,7 @@ public static function buildQuickForm(&$form) { */ public static function formRule($fields, $dontCare, $self) { $errors = []; - + CRM_Core_Error::deprecatedFunctionWarning('no alternative supported for non-core use'); $template = CRM_Core_Smarty::singleton(); if (empty($fields['sms_text_message'])) { @@ -311,9 +319,11 @@ public static function formRule($fields, $dontCare, $self) { * Process the form after the input has been submitted and validated. * * @param CRM_Core_Form $form + * + * @deprecated since 5.71 will be removed around 5.77. */ public static function postProcess(&$form) { - + CRM_Core_Error::deprecatedFunctionWarning('no alternative supported for non-core use'); $thisValues = $form->controller->exportValues($form->getName()); $fromSmsProviderId = $thisValues['sms_provider_id']; @@ -364,7 +374,7 @@ public static function postProcess(&$form) { $contactIds = array_keys($form->_contactDetails); $allContactIds = array_keys($form->_allContactDetails); - list($sent, $activityId, $countSuccess) = CRM_Activity_BAO_Activity::sendSMS($formattedContactDetails, + [$sent, $activityId, $countSuccess] = CRM_Activity_BAO_Activity::sendSMS($formattedContactDetails, $thisValues, $smsParams, $contactIds diff --git a/www/modules/civicrm/CRM/Contact/Form/Task/SMSTrait.php b/www/modules/civicrm/CRM/Contact/Form/Task/SMSTrait.php new file mode 100644 index 000000000..eb6e92e2a --- /dev/null +++ b/www/modules/civicrm/CRM/Contact/Form/Task/SMSTrait.php @@ -0,0 +1,421 @@ +postProcessSms(); + } + + /** + */ + protected function filterContactIDs(): void { + // Activity sub class does this. + } + + /** + * Get SMS provider parameters. + * + * @return array + */ + protected function getSmsProviderParams(): array { + // $smsParams carries all the arguments provided on form (or via hooks), to the provider->send() method + // this gives flexibility to the users / implementors to add their own args via hooks specific to their sms providers + $smsProviderParams = $this->getSubmittedValues(); + unset($smsProviderParams['sms_text_message']); + $smsProviderParams['provider_id'] = $this->getSubmittedValue('sms_provider_id'); + return $smsProviderParams; + } + + /** + * Get phones to SMS. + * + * @internal + * + * @return array + * @throws \CRM_Core_Exception + * @throws \Civi\API\Exception\UnauthorizedException + */ + protected function getPhones(): array { + if (!isset($this->phones)) { + $contacts = []; + $phoneGet = Phone::get() + ->addWhere('contact_id.do_not_sms', '=', FALSE) + ->addWhere('contact_id.is_deceased', '=', FALSE) + ->addWhere('phone_numeric', '>', 0) + ->addWhere('phone_type_id:name', '=', 'Mobile') + ->addOrderBy('is_primary') + ->addSelect('id', 'contact_id', 'phone', 'phone_type_id:name', 'phone_numeric', 'contact_id.sort_name', 'phone_type_id', 'contact_id.display_name'); + if ($this->getSubmittedValue('to')) { + $phoneGet->addWhere('id', 'IN', explode(',', $this->getSubmittedValue('to'))); + } + else { + $phoneGet->addWhere('contact_id', 'IN', $this->getContactIDs()); + } + $this->phones = (array) $phoneGet->execute()->indexBy('id'); + foreach ($this->phones as $index => $phone) { + if ($this->isInvalidRecipient($phone['contact_id'])) { + unset($this->phones[$index]); + } + if (!$this->getSubmittedValue('to')) { + // In this pre-submit case we want to make the contact Ids unique. + if (isset($contacts[$phone['contact_id']])) { + unset($this->phones[$index]); + } + $contacts[$phone['contact_id']] = TRUE; + } + } + } + return $this->phones; + } + + protected function bounceOnNoActiveProviders(): void { + $providersCount = CRM_SMS_BAO_Provider::activeProviderCount(); + if (!$providersCount) { + CRM_Core_Error::statusBounce(ts('There are no SMS providers configured, or no SMS providers are set active')); + } + } + + /** + * Build the SMS Form + * + * @throws \CRM_Core_Exception + * @internal - highly likely to change! + */ + protected function buildSmsForm(): void { + if (!CRM_Core_Permission::check('send SMS')) { + throw new CRM_Core_Exception("You do not have the 'send SMS' permission"); + } + $form = $this; + + $providers = CRM_SMS_BAO_Provider::getProviders(NULL, NULL, TRUE, 'is_default desc'); + + $providerSelect = []; + foreach ($providers as $provider) { + $providerSelect[$provider['id']] = $provider['title']; + } + $suppressedSms = 0; + //here we are getting logged in user id as array but we need target contact id. CRM-5988 + $cid = $form->get('cid'); + + if ($cid) { + $form->_contactIds = [$cid]; + } + + $this->addAutocomplete('to', ts('Send to'), [ + 'entity' => 'Phone', + 'api' => [ + 'fieldName' => 'Phone.id', + ], + 'select' => ['multiple' => TRUE], + 'class' => 'select2', + ], TRUE); + + $form->add('text', 'activity_subject', ts('Name The SMS'), ['class' => 'huge'], TRUE); + + $toSetDefault = TRUE; + if (property_exists($form, '_context') && $form->_context === 'standalone') { + $toSetDefault = FALSE; + } + + // when form is submitted recompute contactIds + if ($this->isSubmitted()) { + $form->_contactIds = []; + foreach ($this->getPhones() as $phone) { + $form->_contactIds[] = $phone['contact_id']; + } + } + + //get the group of contacts as per selected by user in case of Find Activities + $this->filterContactIDs(); + + if (!empty($form->getContactIDs())) { + $phoneNumbers = $this->getPhones(); + $suppressedSms = count($this->getContactIDs()) - count($phoneNumbers); + } + if (!$this->getSubmittedValue('to') && !$this->getPhones()) { + CRM_Core_Error::statusBounce(ts('Selected contact(s) do not have a valid Phone, or communication preferences specify DO NOT SMS, or they are deceased')); + } + + //activity related variables + $form->addExpectedSmartyVariable('invalidActivity'); + $form->addExpectedSmartyVariable('extendTargetContacts'); + + $form->assign('suppressedSms', $suppressedSms); + $form->assign('totalSelectedContacts', count($this->getContactIDs())); + + $form->add('select', 'sms_provider_id', ts('From'), $providerSelect, TRUE); + + CRM_Mailing_BAO_Mailing::commonCompose($form); + + if ($form->_single) { + // also fix the user context stack + if ($form->_context) { + $url = CRM_Utils_System::url('civicrm/dashboard', 'reset=1'); + } + else { + $url = CRM_Utils_System::url('civicrm/contact/view', + "&show=1&action=browse&cid={$form->_contactIds[0]}&selectedChild=activity" + ); + } + + $session = CRM_Core_Session::singleton(); + $session->replaceUserContext($url); + $form->addDefaultButtons(ts('Send SMS'), 'upload', 'cancel'); + } + else { + $form->addDefaultButtons(ts('Send SMS'), 'upload'); + } + + $form->addFormRule([__CLASS__, 'formRuleSms'], $form); + } + + /** + * Set the default form values. + * + * + * @return array + * the default array reference + */ + public function setDefaultValues() { + $phones = $this->getPhones(); + return ['to' => implode(',', array_keys($phones))]; + } + + /** + * Process the sms form after the input has been submitted and validated. + * + * @internal likely to change. + * + * @throws \CRM_Core_Exception + */ + protected function postProcessSms(): void { + $form = $this; + $thisValues = $form->controller->exportValues($form->getName()); + + // process message template + if (!empty($thisValues['SMSsaveTemplate']) || !empty($thisValues['SMSupdateTemplate'])) { + $messageTemplate = [ + 'msg_text' => $this->getSubmittedValue('sms_text_message'), + 'is_active' => TRUE, + 'is_sms' => TRUE, + ]; + + if (!empty($thisValues['SMSsaveTemplate'])) { + $messageTemplate['msg_title'] = $this->getSubmittedValue('SMSsaveTemplateName'); + } + + if ($this->getSubmittedValue('SMStemplate') && $this->getSubmittedValue('SMSupdateTemplate')) { + $messageTemplate['id'] = $this->getSubmittedValue('SMStemplate'); + unset($messageTemplate['msg_title']); + } + MessageTemplate::save()->addRecord($messageTemplate)->execute(); + } + + $phonesToSendTo = explode(',', $this->getSubmittedValue('to')); + $unexpectedErrors = $phonesToSMS = []; + foreach ($phonesToSendTo as $phoneID) { + $phoneValues = $this->getPhones()[$phoneID] ?? NULL; + if (!$phoneValues) { + $unexpectedErrors[] = ts('Contact Does not accept SMS'); + continue; + } + $phonesToSMS[] = $phoneValues; + } + // These errors are unexpected because the ajax should filter them out. + foreach ($unexpectedErrors as $unexpectedError) { + CRM_Core_Session::setStatus($unexpectedError, ts('Invalid phone submitted')); + } + + [$errors, $countSuccess] = $this->sendSMS($phonesToSMS); + if ($countSuccess > 0) { + CRM_Core_Session::setStatus(ts('One message was sent successfully.', [ + 'plural' => '%count messages were sent successfully.', + 'count' => $countSuccess, + ]), ts('Message Sent', ['plural' => 'Messages Sent', 'count' => $countSuccess]), 'success'); + } + + if (!empty($errors)) { + // At least one PEAR_Error object was generated. + // Display the error messages to the user. + $status = '
    '; + foreach ($errors as $errMsg) { + $status .= '
  • ' . $errMsg . '
  • '; + } + $status .= '
'; + CRM_Core_Session::setStatus($status, ts('One Message Not Sent', [ + 'count' => count($errors), + 'plural' => '%count Messages Not Sent', + ]), 'info'); + } + else { + $contactIds = array_values($this->getContactIDs()); + //Display the name and number of contacts for those sms is not sent. + $smsNotSent = array_diff_assoc($this->getContactIDs(), $contactIds); + + if (!empty($smsNotSent)) { + $not_sent = []; + foreach ($smsNotSent as $contactId) { + $displayName = CRM_Utils_String::purifyHTML($this->getValueForContact($contactId, 'display_name')); + $contactViewUrl = CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid=$contactId"); + $not_sent[] = "$displayName"; + } + $status = '(' . ts('because no phone number on file or communication preferences specify DO NOT SMS or Contact is deceased'); + if (CRM_Utils_System::getClassName($form) === 'CRM_Activity_Form_Task_SMS') { + $status .= ' ' . ts("or the contact is not part of the activity '%1'", [1 => $this->getActivityName()]); + } + $status .= ')
  • ' . implode('
  • ', $not_sent) . '
'; + CRM_Core_Session::setStatus($status, ts('One Message Not Sent', [ + 'count' => count($smsNotSent), + 'plural' => '%count Messages Not Sent', + ]), 'info'); + } + } + } + + /** + * Send SMS. + * + * @param array $phones + * + * @return array(array $errors, int $numberSent) + * @throws CRM_Core_Exception + */ + protected function sendSMS(array $phones): array { + + // Create the meta level record first ( sms activity ) + $activityID = Activity::create()->setValues([ + 'source_contact_id' => CRM_Core_Session::getLoggedInContactID(), + 'activity_type_id:name' => 'SMS', + 'activity_date_time' => 'now', + 'subject' => $this->getSubmittedValue('activity_subject'), + 'details' => $this->getSubmittedValue('sms_text_message'), + 'status_id:name' => 'Completed', + ])->execute()->first()['id']; + + $numberSent = 0; + $errors = []; + foreach ($phones as $phone) { + $contactId = $phone['contact_id']; + $tokenText = CRM_Core_BAO_MessageTemplate::renderTemplate(['messageTemplate' => ['msg_text' => $this->getSubmittedValue('sms_text_message')], 'contactId' => $contactId, 'disableSmarty' => TRUE])['text']; + $smsProviderParams = $this->getSmsProviderParams(); + $smsProviderParams['To'] = $phone['phone_numeric']; + try { + CRM_Activity_BAO_Activity::sendSMSMessage( + $contactId, + $tokenText, + $smsProviderParams, + $activityID, + CRM_Core_Session::getLoggedInContactID() + ); + $numberSent++; + } + catch (CRM_Core_Exception $e) { + $errors[] = $e->getMessage(); + } + } + return [$errors, $numberSent]; + } + + /** + * Form rule. + * + * @param array $fields + * The input form values. + * + * @return bool|array + * true if no errors, else array of errors + */ + public static function formRuleSms($fields) { + $errors = []; + + if (empty($fields['sms_text_message'])) { + $errors['sms_text_message'] = ts('Please provide Text message.'); + } + else { + if (!empty($fields['sms_text_message'])) { + $messageCheck = $fields['sms_text_message'] ?? NULL; + $messageCheck = str_replace("\r\n", "\n", $messageCheck); + if ($messageCheck && (strlen($messageCheck) > CRM_SMS_Provider::MAX_SMS_CHAR)) { + $errors['sms_text_message'] = ts("You can configure the SMS message body up to %1 characters", [1 => CRM_SMS_Provider::MAX_SMS_CHAR]); + } + } + } + + //Added for CRM-1393 + if (!empty($fields['SMSsaveTemplate']) && empty($fields['SMSsaveTemplateName'])) { + $errors['SMSsaveTemplateName'] = ts("Enter name to save message template"); + } + + return empty($errors) ? TRUE : $errors; + } + + protected function isInvalidRecipient($contactID): bool { + //Overridden by the activity child class. + return FALSE; + } + + /** + * Get the specified value for the contact. + * + * Note do not rename to `getContactValue()` - that function + * is used on many forms and does not take $id as a parameter. + * + * @param int $contactID + * @param string $value + * + * @return mixed|null + * @throws \CRM_Core_Exception + */ + protected function getValueForContact(int $contactID, $value) { + if (!$this->isDefined('Contact' . $contactID)) { + $this->define('Contact', 'Contact' . $contactID, ['id' => $contactID]); + } + return $this->lookup('Contact' . $contactID, $value); + } + + /** + * List available tokens for this form. + * + * @return array + */ + public function listTokens(): array { + $tokenProcessor = new TokenProcessor(Civi::dispatcher(), ['schema' => ['contactId']]); + return $tokenProcessor->listTokens(); + } + +} diff --git a/www/modules/civicrm/CRM/Contact/Form/Task/SaveSearch.php b/www/modules/civicrm/CRM/Contact/Form/Task/SaveSearch.php index 0014e0347..a80d63916 100644 --- a/www/modules/civicrm/CRM/Contact/Form/Task/SaveSearch.php +++ b/www/modules/civicrm/CRM/Contact/Form/Task/SaveSearch.php @@ -21,6 +21,7 @@ * Saved Searches are used for saving frequently used queries */ class CRM_Contact_Form_Task_SaveSearch extends CRM_Contact_Form_Task { + use CRM_Custom_Form_CustomDataTrait; /** * Saved search id if any. @@ -51,12 +52,7 @@ public function preProcess() { } // Get Task name - $modeValue = CRM_Contact_Form_Search::getModeValue(CRM_Utils_Array::value('component_mode', $values, CRM_Contact_BAO_Query::MODE_CONTACTS)); - $className = $modeValue['taskClassName']; $this->_task = $values['task'] ?? NULL; - - // Add group custom data - CRM_Custom_Form_CustomData::preProcess($this, NULL, NULL, 1, 'Group'); } /** @@ -65,12 +61,17 @@ public function preProcess() { * It consists of * - displaying the QILL (query in local language) * - displaying elements for saving the search + * + * @throws \CRM_Core_Exception */ - public function buildQuickForm() { + public function buildQuickForm(): void { // @todo sync this more with CRM_Group_Form_Edit. $query = new CRM_Contact_BAO_Query($this->get('queryParams')); $this->assign('qill', $query->qill()); + if ($this->isSubmitted()) { + $this->addCustomDataFieldsToForm('Group'); + } // Values from the search form $formValues = $this->controller->exportValues(); @@ -108,9 +109,6 @@ public function buildQuickForm() { CRM_Group_Form_Edit::buildParentGroups($this); CRM_Group_Form_Edit::buildGroupOrganizations($this); - // Build custom data - CRM_Custom_Form_CustomData::buildQuickForm($this); - // get the group id for the saved search $groupID = NULL; if (isset($this->_id)) { @@ -216,7 +214,7 @@ public function postProcess() { $params['id'] = CRM_Contact_BAO_SavedSearch::getName($this->_id, 'id'); } - $params['custom'] = CRM_Core_BAO_CustomField::postProcess($formValues, $this->_id, 'Group'); + $params['custom'] = CRM_Core_BAO_CustomField::postProcess($this->getSubmittedValues(), $this->_id, 'Group'); $group = CRM_Contact_BAO_Group::create($params); @@ -250,7 +248,6 @@ public function setDefaultValues() { if (empty($defaults['parents'])) { $defaults['parents'] = CRM_Core_BAO_Domain::getGroupId(); } - $defaults += CRM_Custom_Form_CustomData::setDefaultValues($this); return $defaults; } diff --git a/www/modules/civicrm/CRM/Contact/Import/Controller.php b/www/modules/civicrm/CRM/Contact/Import/Controller.php deleted file mode 100644 index 7aa08c4ae..000000000 --- a/www/modules/civicrm/CRM/Contact/Import/Controller.php +++ /dev/null @@ -1,41 +0,0 @@ -_stateMachine = new CRM_Import_StateMachine($this, $action); - - // create and instantiate the pages - $this->addPages($this->_stateMachine, $action); - - // add all the actions - $config = CRM_Core_Config::singleton(); - $this->addActions($config->uploadDir, ['uploadFile']); - } - -} diff --git a/www/modules/civicrm/CRM/Contact/Import/Form/MapField.php b/www/modules/civicrm/CRM/Contact/Import/Form/MapField.php index b5be8d05b..c0424febb 100644 --- a/www/modules/civicrm/CRM/Contact/Import/Form/MapField.php +++ b/www/modules/civicrm/CRM/Contact/Import/Form/MapField.php @@ -185,7 +185,7 @@ public function buildQuickForm(): void { $highlightedRelFields[$key][] = $k; } } - $this->assign('highlightedRelFields', $highlightedRelFields); + $this->assign('highlightedRelFields', json_encode($highlightedRelFields)); $sel2[$key] = $this->_formattedFieldNames[$relatedContactType]; if (!empty($relatedContactSubType)) { diff --git a/www/modules/civicrm/CRM/Contact/Import/Parser/Contact.php b/www/modules/civicrm/CRM/Contact/Import/Parser/Contact.php index 8fda4d76a..47167bc42 100644 --- a/www/modules/civicrm/CRM/Contact/Import/Parser/Contact.php +++ b/www/modules/civicrm/CRM/Contact/Import/Parser/Contact.php @@ -388,7 +388,7 @@ private function formatCommonData($params, &$formatted) { foreach ($customOption as $customValue) { $val = $customValue['value'] ?? NULL; $label = strtolower($customValue['label'] ?? ''); - $value = strtolower(trim($formatted[$key])); + $value = strtolower(trim($formatted[$key] ?? '')); if (($value == $label) || ($value == strtolower($val))) { $params[$key] = $formatted[$key] = $val; } diff --git a/www/modules/civicrm/CRM/Contact/Page/AJAX.php b/www/modules/civicrm/CRM/Contact/Page/AJAX.php index 9cacb3677..b1b6602d7 100644 --- a/www/modules/civicrm/CRM/Contact/Page/AJAX.php +++ b/www/modules/civicrm/CRM/Contact/Page/AJAX.php @@ -265,19 +265,6 @@ public static function relationship() { CRM_Utils_JSON::output($ret); } - /** - * Fetch the custom field help. - */ - public static function customField() { - $fieldId = CRM_Utils_Type::escape($_REQUEST['id'], 'Integer'); - $params = ['id' => $fieldId]; - $returnProperties = ['help_pre', 'help_post']; - $values = []; - - CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_CustomField', $params, $values, $returnProperties); - CRM_Utils_JSON::output($values); - } - public static function groupTree() { CRM_Utils_System::setHttpHeader('Content-Type', 'application/json'); $gids = CRM_Utils_Type::escape($_GET['gids'], 'String'); @@ -399,67 +386,6 @@ public static function getContactEmail() { CRM_Utils_System::civiExit(); } - public static function getContactPhone() { - - $queryString = NULL; - $sqlParmas = []; - //check for mobile type - $phoneTypes = CRM_Core_OptionGroup::values('phone_type', TRUE, FALSE, FALSE, NULL, 'name'); - $mobileType = $phoneTypes['Mobile'] ?? NULL; - - $name = CRM_Utils_Request::retrieveValue('name', 'String', NULL, FALSE, 'GET'); - if ($name) { - $key = (int) count(array_keys($sqlParmas)) + 1; - $queryString = " ( cc.sort_name LIKE %{$key} OR cp.phone LIKE %{$key} ) "; - $sqlParams[$key] = ['%' . $name . '%', 'String']; - } - else { - $cid = CRM_Utils_Request::retrieveValue('cid', 'CommaSeparatedIntegers', NULL, FALSE, 'GET'); - if ($cid) { - $queryString = " cc.id IN ( $cid )"; - } - } - - if ($queryString) { - $result = []; - $offset = (int) CRM_Utils_Request::retrieveValue('offset', 'Integer', 0, FALSE, 'GET'); - $rowCount = (int) CRM_Utils_Request::retrieveValue('rowcount', 'Integer', 20, FALSE, 'GET'); - - // add acl clause here - [$aclFrom, $aclWhere] = CRM_Contact_BAO_Contact_Permission::cacheClause('cc'); - if ($aclWhere) { - $aclWhere = " AND $aclWhere"; - } - - $query = " -SELECT sort_name name, cp.phone, cc.id -FROM civicrm_phone cp INNER JOIN civicrm_contact cc ON cc.id = cp.contact_id - {$aclFrom} -WHERE cc.is_deceased = 0 AND cc.do_not_sms = 0 AND cp.phone_type_id = {$mobileType} AND {$queryString} - {$aclWhere} -LIMIT {$offset}, {$rowCount} -"; - - // send query to hook to be modified if needed - CRM_Utils_Hook::contactListQuery($query, - $name, - CRM_Utils_Request::retrieve('context', 'Alphanumeric'), - CRM_Utils_Request::retrieve('cid', 'Positive') - ); - - $dao = CRM_Core_DAO::executeQuery($query, $sqlParams); - - while ($dao->fetch()) { - $result[] = [ - 'text' => '"' . $dao->name . '" (' . $dao->phone . ')', - 'id' => (CRM_Utils_Array::value('id', $_GET)) ? "{$dao->id}::{$dao->phone}" : '"' . $dao->name . '" <' . $dao->phone . '>', - ]; - } - CRM_Utils_JSON::output($result); - } - CRM_Utils_System::civiExit(); - } - public static function buildSubTypes() { $parent = CRM_Utils_Request::retrieve('parentId', 'Positive'); diff --git a/www/modules/civicrm/CRM/Contact/Page/DashBoard.php b/www/modules/civicrm/CRM/Contact/Page/DashBoard.php index 4d6a935a8..ca9bf7974 100644 --- a/www/modules/civicrm/CRM/Contact/Page/DashBoard.php +++ b/www/modules/civicrm/CRM/Contact/Page/DashBoard.php @@ -32,7 +32,7 @@ public function run() { $contentPlacement = CRM_Utils_Hook::DASHBOARD_BELOW; $html = CRM_Utils_Hook::dashboard($contactID, $contentPlacement); if (is_array($html)) { - $this->assign_by_ref('hookContent', $html); + $this->assign('hookContent', $html); $this->assign('hookContentPlacement', $contentPlacement); } diff --git a/www/modules/civicrm/CRM/Contact/Page/DedupeException.php b/www/modules/civicrm/CRM/Contact/Page/DedupeException.php index 091f0550d..db7fb4632 100644 --- a/www/modules/civicrm/CRM/Contact/Page/DedupeException.php +++ b/www/modules/civicrm/CRM/Contact/Page/DedupeException.php @@ -65,7 +65,7 @@ protected function initializePager() { 'pageID' => $this->get(CRM_Utils_Pager::PAGE_ID), ]; $this->_pager = new CRM_Utils_Pager($params); - $this->assign_by_ref('pager', $this->_pager); + $this->assign('pager', $this->_pager); } /** diff --git a/www/modules/civicrm/CRM/Contact/Page/Inline/CustomData.php b/www/modules/civicrm/CRM/Contact/Page/Inline/CustomData.php index fc00e5d07..9644331c5 100644 --- a/www/modules/civicrm/CRM/Contact/Page/Inline/CustomData.php +++ b/www/modules/civicrm/CRM/Contact/Page/Inline/CustomData.php @@ -59,7 +59,7 @@ public function run() { $this->assign('customRecId', $customRecId); $this->assign('contactId', $contactId); $this->assign('customGroupId', $cgId); - $this->assign_by_ref('cd_edit', $fields); + $this->assign('cd_edit', $fields); // check logged in user permission CRM_Contact_Page_View::checkUserPermission($this, $contactId); diff --git a/www/modules/civicrm/CRM/Contact/Page/View/ContactSmartGroup.php b/www/modules/civicrm/CRM/Contact/Page/View/ContactSmartGroup.php index a24811662..cfb61c842 100644 --- a/www/modules/civicrm/CRM/Contact/Page/View/ContactSmartGroup.php +++ b/www/modules/civicrm/CRM/Contact/Page/View/ContactSmartGroup.php @@ -58,10 +58,10 @@ public function browse() { } if (!empty($smart)) { - $this->assign_by_ref('groupSmart', $smart); + $this->assign('groupSmart', $smart); } if (!empty($parent)) { - $this->assign_by_ref('groupParent', $parent); + $this->assign('groupParent', $parent); } } } diff --git a/www/modules/civicrm/CRM/Contact/Page/View/CustomData.php b/www/modules/civicrm/CRM/Contact/Page/View/CustomData.php index dd82e852a..be665b3af 100644 --- a/www/modules/civicrm/CRM/Contact/Page/View/CustomData.php +++ b/www/modules/civicrm/CRM/Contact/Page/View/CustomData.php @@ -42,7 +42,7 @@ public function run() { // If no cid supplied, look it up if (!$this->_contactId && $this->_recId) { - $tableName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $this->_groupId, 'table_name'); + $tableName = CRM_Core_BAO_CustomGroup::getGroup(['id' => $this->_groupId])['table_name'] ?? NULL; if ($tableName) { $this->_contactId = CRM_Core_DAO::singleValueQuery("SELECT entity_id FROM `$tableName` WHERE id = %1", [1 => [$this->_recId, 'Integer']]); } @@ -91,16 +91,16 @@ public function run() { $this->assign('displayStyle', 'tableOriented'); // here the multi custom data listing code will go - $multiRecordFieldListing = TRUE; $page = new CRM_Profile_Page_MultipleRecordFieldsListing(); - $page->set('contactId', $this->_contactId); - $page->set('customGroupId', $this->_groupId); + $page->_contactId = $this->_contactId; + $page->_customGroupId = $this->_groupId; $page->set('action', CRM_Core_Action::BROWSE); - $page->set('multiRecordFieldListing', $multiRecordFieldListing); - $page->set('pageViewType', 'customDataView'); - $page->set('contactType', $ctype); + $page->_pageViewType = 'customDataView'; + $page->_contactType = $ctype; $page->_headersOnly = TRUE; - $page->run(); + // assign vars to templates + $this->assign('action', CRM_Core_Action::BROWSE); + $page->browse(); } else { //Custom Groups Inline diff --git a/www/modules/civicrm/CRM/Contact/Page/View/GroupContact.php b/www/modules/civicrm/CRM/Contact/Page/View/GroupContact.php index 94d0b96f5..e71f68f45 100644 --- a/www/modules/civicrm/CRM/Contact/Page/View/GroupContact.php +++ b/www/modules/civicrm/CRM/Contact/Page/View/GroupContact.php @@ -37,9 +37,9 @@ public function browse() { } $this->assign('groupCount', $count); - $this->assign_by_ref('groupIn', $in); - $this->assign_by_ref('groupPending', $pending); - $this->assign_by_ref('groupOut', $out); + $this->assign('groupIn', $in); + $this->assign('groupPending', $pending); + $this->assign('groupOut', $out); // get the info on contact smart groups $contactSmartGroupSettings = Civi::settings()->get('contact_smart_group_display'); diff --git a/www/modules/civicrm/CRM/Contact/Page/View/Log.php b/www/modules/civicrm/CRM/Contact/Page/View/Log.php index 8133f9edd..fd0632131 100644 --- a/www/modules/civicrm/CRM/Contact/Page/View/Log.php +++ b/www/modules/civicrm/CRM/Contact/Page/View/Log.php @@ -53,7 +53,7 @@ public function browse() { $this->assign('logCount', count($logEntries)); $this->ajaxResponse['tabCount'] = count($logEntries); $this->ajaxResponse += CRM_Contact_Form_Inline::renderFooter($this->_contactId, FALSE); - $this->assign_by_ref('log', $logEntries); + $this->assign('log', $logEntries); } public function preProcess() { diff --git a/www/modules/civicrm/CRM/Contact/Page/View/Summary.php b/www/modules/civicrm/CRM/Contact/Page/View/Summary.php index 8b9c7f08b..01a5851f6 100644 --- a/www/modules/civicrm/CRM/Contact/Page/View/Summary.php +++ b/www/modules/civicrm/CRM/Contact/Page/View/Summary.php @@ -276,7 +276,7 @@ public function view() { // FIXME: when we sort out TZ isssues with DATETIME/TIMESTAMP, we can skip next query // also assign the last modifed details $lastModified = CRM_Core_BAO_Log::lastModified($this->_contactId, 'civicrm_contact'); - $this->assign_by_ref('lastModified', $lastModified); + $this->assign('lastModified', $lastModified); $this->_viewOptions = CRM_Core_BAO_Setting::valueOptions( CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, @@ -285,7 +285,7 @@ public function view() { ); $changeLog = $this->_viewOptions['log']; - $this->assign_by_ref('changeLog', $changeLog); + $this->assign('changeLog', $changeLog); $this->assign('allTabs', $this->getTabs($defaults)); @@ -294,7 +294,7 @@ public function view() { $contentPlacement = CRM_Utils_Hook::SUMMARY_BELOW; CRM_Utils_Hook::summary($this->_contactId, $content, $contentPlacement); if ($content) { - $this->assign_by_ref('hookContent', $content); + $this->assign('hookContent', $content); $this->assign('hookContentPlacement', $contentPlacement); } } @@ -417,18 +417,18 @@ public function getTabs(array $contact) { } // now add all the custom tabs - $entityType = $this->get('contactType'); - $activeGroups = CRM_Core_BAO_CustomGroup::getActiveGroups( - $entityType, - 'civicrm/contact/view/cd', - $this->_contactId - ); + $filters = [ + 'is_active' => TRUE, + 'extends' => $this->get('contactType'), + 'style' => ['Tab', 'Tab with table'], + ]; + $activeGroups = CRM_Core_BAO_CustomGroup::getAll($filters, CRM_Core_Permission::VIEW); foreach ($activeGroups as $group) { $id = "custom_{$group['id']}"; $allTabs[] = [ 'id' => $id, - 'url' => CRM_Utils_System::url($group['path'], $group['query'] . "&selectedChild=$id"), + 'url' => CRM_Utils_System::url('civicrm/contact/view/cd', "reset=1&gid={$group['id']}&cid={$this->_contactId}&selectedChild=$id"), 'title' => $group['title'], 'weight' => $weight, 'count' => NULL, diff --git a/www/modules/civicrm/CRM/Contact/Page/View/UserDashBoard.php b/www/modules/civicrm/CRM/Contact/Page/View/UserDashBoard.php index 5eee298b7..fc3d03c24 100644 --- a/www/modules/civicrm/CRM/Contact/Page/View/UserDashBoard.php +++ b/www/modules/civicrm/CRM/Contact/Page/View/UserDashBoard.php @@ -240,7 +240,7 @@ public static function &links() { // call the hook so we can modify it CRM_Utils_Hook::links('view.contact.userDashBoard', 'Contact', - CRM_Core_DAO::$_nullObject, + NULL, self::$_links ); return self::$_links; diff --git a/www/modules/civicrm/CRM/Contact/Page/View/UserDashBoard/GroupContact.php b/www/modules/civicrm/CRM/Contact/Page/View/UserDashBoard/GroupContact.php index 41a9fdac3..6d010d267 100644 --- a/www/modules/civicrm/CRM/Contact/Page/View/UserDashBoard/GroupContact.php +++ b/www/modules/civicrm/CRM/Contact/Page/View/UserDashBoard/GroupContact.php @@ -53,9 +53,9 @@ public function browse() { ); $this->assign('groupCount', $count); - $this->assign_by_ref('groupIn', $in); - $this->assign_by_ref('groupPending', $pending); - $this->assign_by_ref('groupOut', $out); + $this->assign('groupIn', $in); + $this->assign('groupPending', $pending); + $this->assign('groupOut', $out); } /** diff --git a/www/modules/civicrm/CRM/Contact/Selector.php b/www/modules/civicrm/CRM/Contact/Selector.php index f039446dc..509759be0 100644 --- a/www/modules/civicrm/CRM/Contact/Selector.php +++ b/www/modules/civicrm/CRM/Contact/Selector.php @@ -847,14 +847,14 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $contactUrl = CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$result->contact_id}&key={$this->_key}&context={$this->_context}" ); - $row['contact_type'] = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ? $result->contact_sub_type : $result->contact_type, + $row['contact_type'] = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ?: $result->contact_type, FALSE, $result->contact_id, TRUE, $contactUrl ); - $row['contact_type_orig'] = $result->contact_sub_type ? $result->contact_sub_type : $result->contact_type; + $row['contact_type_orig'] = $result->contact_sub_type ?: $result->contact_type; $row['contact_id'] = $result->contact_id; $row['sort_name'] = $result->sort_name; // Surely this if should be if NOT - otherwise it's just wierd. diff --git a/www/modules/civicrm/CRM/Contact/Task.php b/www/modules/civicrm/CRM/Contact/Task.php index a99254815..83bbe220d 100644 --- a/www/modules/civicrm/CRM/Contact/Task.php +++ b/www/modules/civicrm/CRM/Contact/Task.php @@ -163,7 +163,7 @@ public static function tasks() { ]; //CRM-16329, if SMS provider is configured show sms action. - $providersCount = CRM_SMS_BAO_Provider::activeProviderCount(); + $providersCount = CRM_SMS_BAO_SmsProvider::activeProviderCount(); if ($providersCount && CRM_Core_Permission::check('send SMS')) { self::$_tasks[self::TASK_SMS] = [ 'title' => ts('SMS - schedule/send'), diff --git a/www/modules/civicrm/CRM/Contact/Tokens.php b/www/modules/civicrm/CRM/Contact/Tokens.php index 232f8661a..ec7da87cb 100644 --- a/www/modules/civicrm/CRM/Contact/Tokens.php +++ b/www/modules/civicrm/CRM/Contact/Tokens.php @@ -407,7 +407,7 @@ protected function getTokenMetadata(): array { // Set audience to sysadmin in case adding them to UI annoys people. If people ask to see this // in the UI we could set to 'user'. $field['audience'] = 'sysadmin'; - $field['name'] = $entity . '_billing.' . $field['name']; + $field['name'] = str_replace('_primary.', '_billing.', $field['name']); $this->addFieldToTokenMetadata($tokensMetadata, $field, $exposedFields, $entity . '_billing'); } } @@ -417,6 +417,9 @@ protected function getTokenMetadata(): array { $tokensMetadata['address_primary.state_province_id:abbr'] = $tokensMetadata['address_primary.state_province_id:label']; $tokensMetadata['address_primary.state_province_id:abbr']['name'] = 'address_primary.state_province_id:abbr'; $tokensMetadata['address_primary.state_province_id:abbr']['audience'] = 'user'; + $tokensMetadata['address_billing.state_province_id:abbr'] = $tokensMetadata['address_billing.state_province_id:label'];; + $tokensMetadata['address_billing.state_province_id:abbr']['name'] = 'address_billing.state_province_id:abbr'; + // Hide the label for now because we are not sure if there are paths // where legacy token resolution is in play where this could not be resolved. $tokensMetadata['address_primary.state_province_id:label']['audience'] = 'sysadmin'; @@ -449,6 +452,20 @@ protected function getContact(int $contactId, array $requiredFields, bool $getAl } $joins = []; $customFields = []; + $billingFields = []; + foreach ($requiredFields as $field) { + if (str_contains($field, '_billing.')) { + // Make sure we have enough data to fall back to primary. + $billingEntity = explode('.', $field)[0]; + $billingFields[$field] = $billingEntity; + $extraFields = [$billingEntity . '.id', str_replace('_billing.', '_primary.', $field)]; + foreach ($extraFields as $extraField) { + if (!in_array($extraField, $requiredFields, TRUE)) { + $requiredFields[] = $extraField; + } + } + } + } foreach ($requiredFields as $field) { $fieldSpec = $this->getMetadataForField($field); $prefix = ''; @@ -488,6 +505,11 @@ protected function getContact(int $contactId, array $requiredFields, bool $getAl // fake contact id - check `testReplaceGreetingTokens` return []; } + foreach ($this->getEmptyBillingEntities($billingFields, $contact) as $billingEntityFields) { + foreach ($billingEntityFields as $billingField) { + $contact[$billingField] = $contact[str_replace('_billing.', '_primary.', $billingField)]; + } + } foreach ($this->getDeprecatedTokens() as $apiv3Name => $fieldName) { // it would be set already with the right value for a greeting token @@ -687,6 +709,39 @@ protected function getBespokeTokens(): array { ]; } + /** + * Get the array of related billing entities that are empty. + * + * The billing tokens fall back to the primary address tokens as we cannot rely + * on all contacts having an address with is_billing set and the code historically has + * treated billing addresses as 'get the best billing address', despite the failure + * to set the fields. + * + * Here we figure out the entities where swapping in the primary fields makes sense. + * This is the case when there is no billing address at all, but we don't want to 'supplement' + * a partial billing address with data from a possibly-completely-different primary address. + * + * @param array $billingFields + * @param array $contact + * + * @return array + */ + private function getEmptyBillingEntities(array $billingFields, array $contact): array { + $billingEntitiesToReplaceWithPrimary = []; + foreach ($billingFields as $billingField => $billingEntity) { + // In most cases it is enough to check the 'id' is not present but it is possible + // that a partial address is passed in in preview mode - in which case + // we need to treat the entire address as 'usable'. + if (empty($contact[$billingField]) && empty($contact[$billingEntity . '.id'])) { + $billingEntitiesToReplaceWithPrimary[$billingEntity][] = $billingField; + } + else { + unset($billingEntitiesToReplaceWithPrimary[$billingEntity]); + } + } + return $billingEntitiesToReplaceWithPrimary; + } + /** * Get the tokens defined by the legacy hook. * diff --git a/www/modules/civicrm/CRM/Contribute/ActionMapping.php b/www/modules/civicrm/CRM/Contribute/ActionMapping.php index 625fc1af5..f4f667632 100644 --- a/www/modules/civicrm/CRM/Contribute/ActionMapping.php +++ b/www/modules/civicrm/CRM/Contribute/ActionMapping.php @@ -24,7 +24,7 @@ public function getEntityName(): string { return 'Contribution'; } - public function modifySpec(\Civi\Api4\Service\Spec\RequestSpec $spec) { + public function modifyApiSpec(\Civi\Api4\Service\Spec\RequestSpec $spec) { $spec->getFieldByName('entity_status') ->setLabel(ts('Contribution Status')); } diff --git a/www/modules/civicrm/CRM/Contribute/ActionMapping/ByPage.php b/www/modules/civicrm/CRM/Contribute/ActionMapping/ByPage.php index 7254291b8..e5b670d29 100644 --- a/www/modules/civicrm/CRM/Contribute/ActionMapping/ByPage.php +++ b/www/modules/civicrm/CRM/Contribute/ActionMapping/ByPage.php @@ -36,8 +36,8 @@ public function getLabel(): string { return ts('Contribution Page'); } - public function modifySpec(\Civi\Api4\Service\Spec\RequestSpec $spec) { - parent::modifySpec($spec); + public function modifyApiSpec(\Civi\Api4\Service\Spec\RequestSpec $spec) { + parent::modifyApiSpec($spec); $spec->getFieldByName('entity_value') ->setLabel(ts('Contribution Page')); } diff --git a/www/modules/civicrm/CRM/Contribute/ActionMapping/ByType.php b/www/modules/civicrm/CRM/Contribute/ActionMapping/ByType.php index 0434b8f25..3a78a6a49 100644 --- a/www/modules/civicrm/CRM/Contribute/ActionMapping/ByType.php +++ b/www/modules/civicrm/CRM/Contribute/ActionMapping/ByType.php @@ -36,8 +36,8 @@ public function getLabel(): string { return ts('Contribution Type'); } - public function modifySpec(\Civi\Api4\Service\Spec\RequestSpec $spec) { - parent::modifySpec($spec); + public function modifyApiSpec(\Civi\Api4\Service\Spec\RequestSpec $spec) { + parent::modifyApiSpec($spec); $spec->getFieldByName('entity_value') ->setLabel(ts('Financial Type')); $spec->getFieldByName('recipient_listing') diff --git a/www/modules/civicrm/CRM/Contribute/BAO/Contribution.php b/www/modules/civicrm/CRM/Contribute/BAO/Contribution.php index 71a69631a..613e76048 100644 --- a/www/modules/civicrm/CRM/Contribute/BAO/Contribution.php +++ b/www/modules/civicrm/CRM/Contribute/BAO/Contribution.php @@ -14,8 +14,10 @@ use Civi\Api4\Address; use Civi\Api4\Contribution; use Civi\Api4\ContributionRecur; +use Civi\Api4\EntityFinancialTrxn; use Civi\Api4\LineItem; use Civi\Api4\ContributionSoft; +use Civi\Api4\MembershipLog; use Civi\Api4\PaymentProcessor; use Civi\Core\Event\PostEvent; @@ -24,7 +26,7 @@ * @package CRM * @copyright CiviCRM LLC https://civicrm.org/licensing */ -class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution implements Civi\Test\HookInterface { +class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution implements Civi\Core\HookInterface { /** * Static field for all the contribution information that we can potentially import @@ -119,10 +121,11 @@ public static function add(&$params) { if (!$contributionStatusID) { // Since the fee amount is expecting this (later on) ensure it is always set. // It would only not be set for an update where it is unchanged. - $params['contribution_status_id'] = civicrm_api3('Contribution', 'getvalue', [ - 'id' => $contributionID, - 'return' => 'contribution_status_id', - ]); + $params['contribution_status_id'] = Contribution::get(FALSE) + ->addSelect('contribution_status_id') + ->addWhere('id', '=', $contributionID) + ->execute() + ->first()['contribution_status_id'] ?? NULL; } $contributionStatus = CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', (int) $params['contribution_status_id']); @@ -260,8 +263,7 @@ public static function isUpdateToRecurringContribution($params) { if (empty($params['contribution_recur_id']) && empty($params['prevContribution']->contribution_recur_id)) { return FALSE; } - $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'); - if ($params['prevContribution']->contribution_status_id == array_search('Pending', $contributionStatus)) { + if ($params['prevContribution']->contribution_status_id == CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending')) { return TRUE; } return FALSE; @@ -373,10 +375,11 @@ public static function calculateMissingAmountParams(&$params, $contributionID) { if (isset($params['fee_amount']) || isset($params['total_amount'])) { // We have an existing contribution and fee_amount or total_amount has been passed in but not net_amount. // net_amount may need adjusting. - $contribution = civicrm_api3('Contribution', 'getsingle', [ - 'id' => $contributionID, - 'return' => ['total_amount', 'net_amount', 'fee_amount'], - ]); + $contribution = Contribution::get(FALSE) + ->addSelect('total_amount', 'net_amount', 'fee_amount') + ->addWhere('id', '=', $contributionID) + ->execute() + ->first(); $totalAmount = (isset($params['total_amount']) ? (float) $params['total_amount'] : (float) CRM_Utils_Array::value('total_amount', $contribution)); $feeAmount = (isset($params['fee_amount']) ? (float) $params['fee_amount'] : (float) CRM_Utils_Array::value('fee_amount', $contribution)); $params['net_amount'] = $totalAmount - $feeAmount; @@ -452,10 +455,11 @@ public static function getPaymentProcessorReadyAddressParams($params) { * @param int $membershipTypeID * * @param int $contributionID - * + * @deprecated * @return int */ public static function getNumTermsByContributionAndMembershipType($membershipTypeID, $contributionID) { + CRM_Core_Error::deprecatedFunctionWarning('Use API4 LineItem::get'); $numTerms = CRM_Core_DAO::singleValueQuery(" SELECT v.membership_num_terms FROM civicrm_line_item li LEFT JOIN civicrm_price_field_value v ON li.price_field_value_id = v.id @@ -1271,7 +1275,7 @@ public static function failPayment($contributionID, $contactID, $message) { 'details' => $message, 'subject' => ts('Payment failed at payment processor'), 'source_record_id' => $contributionID, - 'source_contact_id' => CRM_Core_Session::getLoggedInContactID() ? CRM_Core_Session::getLoggedInContactID() : $contactID, + 'source_contact_id' => CRM_Core_Session::getLoggedInContactID() ?: $contactID, ]); // CRM-20336 Make sure that the contribution status is Failed, not Pending. @@ -1596,10 +1600,13 @@ public static function checkDuplicateIds($params) { * @param array $componentIds * Component ids. * + * @deprecated since 5.72 will be removed around 5.90 + * * @return array * associated array */ public static function getContributionDetails($exportMode, $componentIds) { + CRM_Core_Error::deprecatedFunctionWarning('no alternative'); $paymentDetails = []; $componentClause = ' IN ( ' . implode(',', $componentIds) . ' ) '; @@ -1833,10 +1840,7 @@ public static function transitionComponents($params) { } } - $paymentProcessorID = CRM_Utils_Array::value('payment_processor_id', $input, CRM_Utils_Array::value( - 'paymentProcessor', - $ids - )); + $paymentProcessorID = $input['payment_processor_id'] ?? $ids['paymentProcessor'] ?? NULL; if (!isset($input['payment_processor_id']) && !$paymentProcessorID && $contribution->contribution_page_id) { $paymentProcessorID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_ContributionPage', @@ -2964,29 +2968,27 @@ public function _assignMessageVariablesToTemplate(&$values, $input, $returnMessa 'label' => $this->_relatedObjects['participant']->fee_level . ' - ' . $primaryEmail, 'amount' => $this->_relatedObjects['participant']->fee_amount, ]; + $primaryParticipantID = $this->_relatedObjects['participant']->id; + $additionalIDs = CRM_Event_BAO_Participant::getAdditionalParticipantIds($primaryParticipantID); //build an array of cId/pId of participants - $additionalIDs = CRM_Event_BAO_Event::buildCustomProfile($this->_relatedObjects['participant']->id, NULL, $this->_relatedObjects['contact']->id, $isTest, TRUE); - unset($additionalIDs[$this->_relatedObjects['participant']->id]); //send receipt to additional participant if exists if (count($additionalIDs)) { $template->assign('isPrimary', 0); $template->assign('customProfile', NULL); //set additionalParticipant true $values['params']['additionalParticipant'] = TRUE; - foreach ($additionalIDs as $pId => $cId) { + foreach ($additionalIDs as $participantID) { $amount = []; //to change the status pending to completed $additional = new CRM_Event_DAO_Participant(); - $additional->id = $pId; - $additional->contact_id = $cId; + $additional->id = $participantID; $additional->find(TRUE); - $additional->register_date = $this->_relatedObjects['participant']->register_date; - $additional->status_id = 1; + $contactID = (int) $additional->contact_id; $additionalParticipantInfo = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_Email', $additional->contact_id, 'email', 'contact_id'); //if additional participant dont have email //use display name. if (!$additionalParticipantInfo) { - $additionalParticipantInfo = CRM_Contact_BAO_Contact::displayName($additional->contact_id); + $additionalParticipantInfo = CRM_Contact_BAO_Contact::displayName($contactID); } $amount[0] = [ 'label' => $additional->fee_level, @@ -2996,9 +2998,8 @@ public function _assignMessageVariablesToTemplate(&$values, $input, $returnMessa 'label' => $additional->fee_level . ' - ' . $additionalParticipantInfo, 'amount' => $additional->fee_amount, ]; - $additional->save(); $template->assign('amount', $amount); - CRM_Event_BAO_Event::sendMail($cId, $values, $pId, $isTest, $returnMessageText); + CRM_Event_BAO_Event::sendMail($contactID, $values, $participantID, $isTest, $returnMessageText); } } @@ -3075,8 +3076,8 @@ public static function isSubscriptionCancelled($contributionId) { WHERE con.id = %1 LIMIT 1"; $params = [1 => [$contributionId, 'Integer']]; $statusId = CRM_Core_DAO::singleValueQuery($sql, $params); - $status = CRM_Contribute_PseudoConstant::contributionStatus($statusId, 'name'); - if ($status == 'Cancelled') { + $status = CRM_Core_Pseudoconstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $statusId); + if ($status === 'Cancelled') { return TRUE; } return FALSE; @@ -3182,14 +3183,14 @@ public static function recordFinancialAccounts(&$params, CRM_Contribute_DAO_Cont 'trxn_date' => $params['receive_date'] ?? date('YmdHis'), 'total_amount' => $totalAmount, 'fee_amount' => $params['fee_amount'] ?? NULL, - 'net_amount' => CRM_Utils_Array::value('net_amount', $params, $totalAmount), + 'net_amount' => $params['net_amount'] ?? $totalAmount, 'currency' => $contribution->currency, 'trxn_id' => $contribution->trxn_id, // @todo - this is getting the status id from the contribution - that is BAD - ie the contribution could be partially // paid but each payment is completed. The work around is to pass in the status_id in the trxn_params but // this should really default to completed (after discussion). 'status_id' => $statusId, - 'payment_instrument_id' => CRM_Utils_Array::value('payment_instrument_id', $params, $params['contribution']->payment_instrument_id), + 'payment_instrument_id' => $params['payment_instrument_id'] ?? $params['contribution']->payment_instrument_id, 'check_number' => $params['check_number'] ?? NULL, 'pan_truncation' => $params['pan_truncation'] ?? NULL, 'card_type_id' => $params['card_type_id'] ?? NULL, @@ -3269,7 +3270,7 @@ public static function recordFinancialAccounts(&$params, CRM_Contribute_DAO_Cont $line['financial_type_id'] = $params['financial_type_id']; } } - CRM_Core_BAO_FinancialTrxn::createDeferredTrxn(CRM_Utils_Array::value('line_item', $params), $params['contribution'], TRUE, 'changeFinancialType'); + CRM_Core_BAO_FinancialTrxn::createDeferredTrxn($params['line_item'] ?? NULL, $params['contribution'], TRUE, 'changeFinancialType'); /* $params['trxnParams']['to_financial_account_id'] = $trxnParams['to_financial_account_id']; */ $params['financial_account_id'] = $newFinancialAccount; $params['total_amount'] = $params['trxnParams']['total_amount'] = $params['trxnParams']['net_amount'] = $trxnParams['total_amount']; @@ -3278,7 +3279,7 @@ public static function recordFinancialAccounts(&$params, CRM_Contribute_DAO_Cont $params['trxnParams']['fee_amount'] = $params['fee_amount']; } CRM_Contribute_BAO_FinancialProcessor::updateFinancialAccounts($params); - CRM_Core_BAO_FinancialTrxn::createDeferredTrxn(CRM_Utils_Array::value('line_item', $params), $params['contribution'], TRUE); + CRM_Core_BAO_FinancialTrxn::createDeferredTrxn($params['line_item'] ?? NULL, $params['contribution'], TRUE); $params['trxnParams']['to_financial_account_id'] = $trxnParams['to_financial_account_id']; $updated = TRUE; $params['deferred_financial_account_id'] = $newFinancialAccount; @@ -3300,7 +3301,7 @@ public static function recordFinancialAccounts(&$params, CRM_Contribute_DAO_Cont $callUpdateFinancialAccounts = CRM_Contribute_BAO_FinancialProcessor::updateFinancialAccountsOnContributionStatusChange($params); if ($callUpdateFinancialAccounts) { CRM_Contribute_BAO_FinancialProcessor::updateFinancialAccounts($params, 'changedStatus'); - CRM_Core_BAO_FinancialTrxn::createDeferredTrxn(CRM_Utils_Array::value('line_item', $params), $params['contribution'], TRUE, 'changedStatus'); + CRM_Core_BAO_FinancialTrxn::createDeferredTrxn($params['line_item'] ?? NULL, $params['contribution'], TRUE, 'changedStatus'); } $updated = TRUE; } @@ -3329,7 +3330,7 @@ public static function recordFinancialAccounts(&$params, CRM_Contribute_DAO_Cont //Update Financial Records $params['trxnParams']['from_financial_account_id'] = NULL; CRM_Contribute_BAO_FinancialProcessor::updateFinancialAccounts($params, 'changedAmount'); - CRM_Core_BAO_FinancialTrxn::createDeferredTrxn(CRM_Utils_Array::value('line_item', $params), $params['contribution'], TRUE, 'changedAmount'); + CRM_Core_BAO_FinancialTrxn::createDeferredTrxn($params['line_item'] ?? NULL, $params['contribution'], TRUE, 'changedAmount'); $updated = TRUE; } @@ -3390,7 +3391,7 @@ public static function recordFinancialAccounts(&$params, CRM_Contribute_DAO_Cont && $params['prevContribution']->contribution_status_id != $params['contribution']->contribution_status_id ) { $eventID = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Participant', $entityId, 'event_id'); - $feeLevel[] = str_replace('', '', $params['prevContribution']->amount_level); + $feeLevel = str_replace('�', '', $params['prevContribution']->amount_level); CRM_Event_BAO_Participant::createDiscountTrxn($eventID, $params, $feeLevel); } unset($params['line_item']); @@ -3623,18 +3624,14 @@ public static function getPaymentInfo($id, $component = 'contribution', $getTrxn $paymentBalance = CRM_Contribute_BAO_Contribution::getContributionBalance($contributionId, $total); - $contribution = civicrm_api3('Contribution', 'getsingle', [ - 'id' => $contributionId, - 'return' => [ - 'currency', - 'is_pay_later', - 'contribution_status_id', - 'financial_type_id', - ], - ]); + $contribution = Contribution::get(FALSE) + ->addSelect('currency', 'is_pay_later', 'contribution_status_id:name', 'financial_type_id') + ->addWhere('id', '=', $contributionId) + ->execute() + ->first(); $info['payLater'] = $contribution['is_pay_later']; - $info['contribution_status'] = $contribution['contribution_status']; + $info['contribution_status'] = $contribution['contribution_status_id:name']; $info['currency'] = $contribution['currency']; $info['total'] = $total; @@ -4173,7 +4170,9 @@ private static function getOriginalContribution($contributionID) { * load them in this function. Code clean up would compensate for any minor performance implication. * * @param int $contributionID + * The Contribution ID that was Completed * @param string $changeDate + * If provided, specify an alternative date to use as "today" calculation of membership dates * * @throws \CRM_Core_Exception */ @@ -4196,30 +4195,29 @@ public static function updateMembershipBasedOnCompletionOfContribution($contribu // CRM-8141 update the membership type with the value recorded in log when membership created/renewed // this picks up membership type changes during renewals - // @todo this is almost certainly an obsolete sql call, the pre-change - // membership is accessible via $this->_relatedObjects - $sql = " -SELECT membership_type_id -FROM civicrm_membership_log -WHERE membership_id={$membershipParams['id']} -ORDER BY id DESC -LIMIT 1;"; - $dao = CRM_Core_DAO::executeQuery($sql); - if ($dao->fetch()) { - if (!empty($dao->membership_type_id)) { - $membershipParams['membership_type_id'] = $dao->membership_type_id; - } + $preChangeMembership = MembershipLog::get(FALSE) + ->addSelect('membership_type_id') + ->addWhere('membership_id', '=', $membershipParams['id']) + ->addOrderBy('id', 'DESC') + ->execute() + ->first(); + if (!empty($preChangeMembership) && !empty($preChangeMembership['membership_type_id'])) { + $membershipParams['membership_type_id'] = $preChangeMembership['membership_type_id']; } if (empty($membership['end_date']) || (int) $membership['status_id'] !== CRM_Core_PseudoConstant::getKey('CRM_Member_BAO_Membership', 'status_id', 'Pending')) { // Passing num_terms to the api triggers date calculations, but for pending memberships these may be already calculated. // sigh - they should be consistent but removing the end date check causes test failures & maybe UI too? // The api assumes num_terms is a special sauce for 'is_renewal' so we need to not pass it when updating a pending to completed. // ... except testCompleteTransactionMembershipPriceSetTwoTerms hits this line so the above is obviously not true.... - // @todo once apiv4 ships with core switch to that & find sanity. - $membershipParams['num_terms'] = self::getNumTermsByContributionAndMembershipType( - $membershipParams['membership_type_id'], - $contributionID - ); + $lineItem = LineItem::get(FALSE) + ->addSelect('membership_num_terms') + ->addJoin('PriceFieldValue AS price_field_value', 'LEFT') + ->addWhere('contribution_id', '=', $contributionID) + ->addWhere('price_field_value.membership_type_id', '=', $membershipParams['membership_type_id']) + ->execute() + ->first(); + // default of 1 is precautionary + $membershipParams['num_terms'] = empty($lineItem['membership_num_terms']) ? 1 : $lineItem['membership_num_terms']; } // @todo remove all this stuff in favour of letting the api call further down handle in // (it is a duplication of what the api does). @@ -4256,7 +4254,7 @@ public static function updateMembershipBasedOnCompletionOfContribution($contribu ); unset($dates['end_date']); - $membershipParams['status_id'] = CRM_Utils_Array::value('id', $calcStatus, 'New'); + $membershipParams['status_id'] = $calcStatus['id'] ?? 'New'; } //we might be renewing membership, //so make status override false. @@ -4523,14 +4521,13 @@ public static function getSalesTaxFinancialAccounts() { * * @throws \CRM_Core_Exception */ - public static function createProportionalEntry($entityParams, $eftParams) { - $paid = 0; + public static function createProportionalEntry(array $entityParams, array $eftParams): void { + $eftParams['amount'] = 0; if ($entityParams['contribution_total_amount'] != 0) { - $paid = $entityParams['line_item_amount'] * ($entityParams['trxn_total_amount'] / $entityParams['contribution_total_amount']); + $eftParams['amount'] = $entityParams['line_item_amount'] * ($entityParams['trxn_total_amount'] / $entityParams['contribution_total_amount']); } // Record Entity Financial Trxn; CRM-20145 - $eftParams['amount'] = $paid; - civicrm_api3('EntityFinancialTrxn', 'create', $eftParams); + EntityFinancialTrxn::create(FALSE)->setValues($eftParams)->execute(); } /** @@ -4624,53 +4621,6 @@ protected function loadRelatedEntitiesByID($ids) { } } - /** - * Do not use - unused in core. - * - * Function to replace contribution tokens. - * - * @param array $contributionIds - * - * @param string $subject - * - * @param array $subjectToken - * - * @param string $text - * - * @param string $html - * - * @param array $messageToken - * - * @param bool $escapeSmarty - * - * @deprecated - * - * @return array - * @throws \CRM_Core_Exception - */ - public static function replaceContributionTokens( - $contributionIds, - $subject, - $subjectToken, - $text, - $html, - $messageToken, - $escapeSmarty - ) { - CRM_Core_Error::deprecatedFunctionWarning('use the TokenProcessor'); - if (empty($contributionIds)) { - return []; - } - $contributionDetails = []; - foreach ($contributionIds as $id) { - $result = self::getContributionTokenValues($id, $messageToken); - $contributionDetails[$result['values'][$result['id']]['contact_id']]['subject'] = CRM_Utils_Token::replaceContributionTokens($subject, $result, FALSE, $subjectToken, FALSE, $escapeSmarty); - $contributionDetails[$result['values'][$result['id']]['contact_id']]['text'] = CRM_Utils_Token::replaceContributionTokens($text, $result, FALSE, $messageToken, FALSE, $escapeSmarty); - $contributionDetails[$result['values'][$result['id']]['contact_id']]['html'] = CRM_Utils_Token::replaceContributionTokens($html, $result, FALSE, $messageToken, FALSE, $escapeSmarty); - } - return $contributionDetails; - } - /** * Do not use - still called from CRM_Contribute_Form_Task_PDFLetter * diff --git a/www/modules/civicrm/CRM/Contribute/BAO/ContributionPage.php b/www/modules/civicrm/CRM/Contribute/BAO/ContributionPage.php index 59dcd31af..b1e8278fd 100644 --- a/www/modules/civicrm/CRM/Contribute/BAO/ContributionPage.php +++ b/www/modules/civicrm/CRM/Contribute/BAO/ContributionPage.php @@ -313,18 +313,14 @@ public static function sendMail($contactID, $values, $isTest = FALSE, $returnMes } [$values['customPost_grouptitle'], $values['customPost']] = self::getProfileNameAndFields($postID, $userID, $params['custom_post_id']); } - // Assign honoree values for the receipt. But first, stop any leaks from - // previously assigned values. - $template->assign('honoreeProfile', []); - $template->assign('honorName', NULL); - if (isset($values['honor'])) { - $honorValues = $values['honor']; - $template->_values = ['honoree_profile_id' => $values['honoree_profile_id']]; - CRM_Contribute_BAO_ContributionSoft::formatHonoreeProfileFields( - $template, - $honorValues['honor_profile_values'], - $honorValues['honor_id'] - ); + // Assign honoree values for the receipt. + $honorValues = $values['honor'] ?? ['honor_profile_id' => NULL, 'honor_id' => NULL, 'honor_profile_values' => []]; + foreach (CRM_Contribute_BAO_ContributionSoft::getHonorTemplateVariables( + $honorValues['honor_profile_id'] ? (int) $honorValues['honor_profile_id'] : NULL, + $honorValues['honor_id'] ? (int) $honorValues['honor_id'] : NULL, + $honorValues['honor_profile_values'] ?? [], + ) as $honorFieldName => $honorFieldValue) { + $template->assign($honorFieldName, $honorFieldValue); } $title = $values['title'] ?? CRM_Contribute_BAO_Contribution_Utils::getContributionPageTitle($values['contribution_page_id']); diff --git a/www/modules/civicrm/CRM/Contribute/BAO/ContributionRecur.php b/www/modules/civicrm/CRM/Contribute/BAO/ContributionRecur.php index df1dea918..3d139f7d5 100644 --- a/www/modules/civicrm/CRM/Contribute/BAO/ContributionRecur.php +++ b/www/modules/civicrm/CRM/Contribute/BAO/ContributionRecur.php @@ -19,7 +19,7 @@ * @package CRM * @copyright CiviCRM LLC https://civicrm.org/licensing */ -class CRM_Contribute_BAO_ContributionRecur extends CRM_Contribute_DAO_ContributionRecur implements Civi\Test\HookInterface { +class CRM_Contribute_BAO_ContributionRecur extends CRM_Contribute_DAO_ContributionRecur implements Civi\Core\HookInterface { /** * Create recurring contribution. @@ -333,12 +333,11 @@ public static function cancelRecurContribution(array $params): bool { } /** - * @param int $entityID - * @param string $entity + * @param int $recurringContributionID * * @return null|Object */ - public static function getSubscriptionDetails($entityID, $entity = 'recur') { + public static function getSubscriptionDetails($recurringContributionID) { // Note: processor_id used to be aliased as subscription_id so we include it here // both as processor_id and subscription_id for legacy compatibility. $sql = " @@ -360,35 +359,13 @@ public static function getSubscriptionDetails($entityID, $entity = 'recur') { con.id as contribution_id, con.contribution_page_id, rec.contact_id, - mp.membership_id"; - - if ($entity == 'recur') { - // This should be always true now. - $sql .= " + mp.membership_id FROM civicrm_contribution_recur rec LEFT JOIN civicrm_contribution con ON ( con.contribution_recur_id = rec.id ) LEFT JOIN civicrm_membership_payment mp ON ( mp.contribution_id = con.id ) WHERE rec.id = %1"; - } - elseif ($entity == 'contribution') { - CRM_Core_Error::deprecatedWarning('no longer used'); - $sql .= " - FROM civicrm_contribution con -INNER JOIN civicrm_contribution_recur rec ON ( con.contribution_recur_id = rec.id ) -LEFT JOIN civicrm_membership_payment mp ON ( mp.contribution_id = con.id ) - WHERE con.id = %1"; - } - elseif ($entity == 'membership') { - CRM_Core_Error::deprecatedWarning('no longer used'); - $sql .= " - FROM civicrm_membership_payment mp -INNER JOIN civicrm_membership mem ON ( mp.membership_id = mem.id ) -INNER JOIN civicrm_contribution_recur rec ON ( mem.contribution_recur_id = rec.id ) -INNER JOIN civicrm_contribution con ON ( con.id = mp.contribution_id ) - WHERE mp.membership_id = %1"; - } - $dao = CRM_Core_DAO::executeQuery($sql, [1 => [$entityID, 'Integer']]); + $dao = CRM_Core_DAO::executeQuery($sql, [1 => [$recurringContributionID, 'Integer']]); if ($dao->fetch()) { return $dao; } @@ -919,38 +896,33 @@ public static function getRecurringFields() { * * @throws \CRM_Core_Exception */ - public static function updateOnNewPayment($recurringContributionID, $paymentStatus, string $effectiveDate = 'now') { - + public static function updateOnNewPayment($recurringContributionID, string $paymentStatus, string $effectiveDate = 'now') { if (!in_array($paymentStatus, ['Completed', 'Failed'])) { return; } - $params = [ - 'id' => $recurringContributionID, - 'return' => [ - 'contribution_status_id', - 'next_sched_contribution_date', - 'frequency_unit', - 'frequency_interval', - 'installments', - 'failure_count', - ], - ]; - $existing = civicrm_api3('ContributionRecur', 'getsingle', $params); + $existingRecur = \Civi\Api4\ContributionRecur::get(FALSE) + ->addSelect('contribution_status_id:name', 'next_sched_contribution_date', 'frequency_unit', 'frequency_interval', 'installments', 'failure_count') + ->addWhere('id', '=', $recurringContributionID) + ->execute() + ->first(); - if ($paymentStatus == 'Completed' - && CRM_Contribute_PseudoConstant::contributionStatus($existing['contribution_status_id'], 'name') == 'Pending') { - $params['contribution_status_id'] = 'In Progress'; + $updatedRecurParams['id'] = $recurringContributionID; + if (($paymentStatus === 'Completed') + && ($existingRecur['contribution_status_id:name'] === 'Pending')) { + // Update Recur to "In Progress" + $updatedRecurParams['contribution_status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_ContributionRecur', 'contribution_status_id', 'In Progress'); } if ($paymentStatus == 'Failed') { - $params['failure_count'] = $existing['failure_count']; + $updatedRecurParams['failure_count'] = $existingRecur['failure_count']; } - $params['modified_date'] = date('Y-m-d H:i:s'); + $updatedRecurParams['modified_date'] = date('Y-m-d H:i:s'); - if (!empty($existing['installments']) && self::isComplete($recurringContributionID, $existing['installments'])) { - $params['contribution_status_id'] = 'Completed'; - $params['next_sched_contribution_date'] = 'null'; - $params['end_date'] = 'now'; + if (!empty($existingRecur['installments']) && self::isComplete($recurringContributionID, $existingRecur['installments'])) { + // Update Recur to "Completed" + $updatedRecurParams['contribution_status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_ContributionRecur', 'contribution_status_id', 'Completed'); + $updatedRecurParams['next_sched_contribution_date'] = 'null'; + $updatedRecurParams['end_date'] = 'now'; } else { // Only update next sched date if it's empty or up to 48 hours away because payment processors may be managing @@ -960,12 +932,12 @@ public static function updateOnNewPayment($recurringContributionID, $paymentStat // Note 48 hours is a bit aribtrary but means that we can hopefully ignore the time being potentially // rounded down to midnight. $upperDateToConsiderProcessed = strtotime('+ 48 hours', ($effectiveDate ? strtotime($effectiveDate) : time())); - if (empty($existing['next_sched_contribution_date']) || strtotime($existing['next_sched_contribution_date']) <= + if (empty($existingRecur['next_sched_contribution_date']) || strtotime($existingRecur['next_sched_contribution_date']) <= $upperDateToConsiderProcessed) { - $params['next_sched_contribution_date'] = date('Y-m-d', strtotime('+' . $existing['frequency_interval'] . ' ' . $existing['frequency_unit'], strtotime($effectiveDate))); + $updatedRecurParams['next_sched_contribution_date'] = date('Y-m-d', strtotime('+' . $existingRecur['frequency_interval'] . ' ' . $existingRecur['frequency_unit'], strtotime($effectiveDate))); } } - civicrm_api3('ContributionRecur', 'create', $params); + civicrm_api3('ContributionRecur', 'create', $updatedRecurParams); } /** diff --git a/www/modules/civicrm/CRM/Contribute/BAO/ContributionSoft.php b/www/modules/civicrm/CRM/Contribute/BAO/ContributionSoft.php index 4fbad1a2f..7fb574b34 100644 --- a/www/modules/civicrm/CRM/Contribute/BAO/ContributionSoft.php +++ b/www/modules/civicrm/CRM/Contribute/BAO/ContributionSoft.php @@ -484,67 +484,15 @@ public static function formatHonoreeProfileFields($form, $params, $honorId = NUL if (empty($form->_values['honoree_profile_id'])) { return; } - $profileContactType = CRM_Core_BAO_UFGroup::getContactType($form->_values['honoree_profile_id']); - $profileFields = CRM_Core_BAO_UFGroup::getFields($form->_values['honoree_profile_id']); - $honoreeProfileFields = $values = []; - $honorName = NULL; - - if ($honorId) { - CRM_Core_BAO_UFGroup::getValues($honorId, $profileFields, $values, FALSE, $params); - if (empty($params)) { - foreach ($profileFields as $name => $field) { - $title = $field['title']; - $params[$field['name']] = $values[$title]; - } - } - } + $profileID = $form->_values['honoree_profile_id']; if (!is_array($params)) { CRM_Core_Error::deprecatedWarning('this could indicate a bug - see https://lab.civicrm.org/dev/core/-/issues/4881'); $params = []; } + $honoreeVariables = self::getHonorTemplateVariables($profileID, $honorId, $params); - //remove name related fields and construct name string with prefix/suffix - //which will be later assigned to template - switch ($profileContactType) { - case 'Individual': - if (array_key_exists('prefix_id', $params)) { - $honorName = CRM_Utils_Array::value($params['prefix_id'], - CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'prefix_id') - ); - unset($profileFields['prefix_id']); - } - $honorName .= ' ' . $params['first_name'] . ' ' . $params['last_name']; - unset($profileFields['first_name']); - unset($profileFields['last_name']); - if (array_key_exists('suffix_id', $params)) { - $honorName .= ' ' . CRM_Utils_Array::value($params['suffix_id'], - CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'suffix_id') - ); - unset($profileFields['suffix_id']); - } - break; - - case 'Organization': - $honorName = $params['organization_name']; - unset($profileFields['organization_name']); - break; - - case 'Household': - $honorName = $params['household_name']; - unset($profileFields['household_name']); - break; - } - - if ($honorId) { - $honoreeProfileFields['Name'] = $honorName; - foreach ($profileFields as $name => $field) { - $title = $field['title']; - $honoreeProfileFields[$title] = $values[$title]; - } - $form->assign('honoreeProfile', $honoreeProfileFields); - } - else { - $form->assign('honorName', $honorName); + foreach ($honoreeVariables as $honorField => $honorValue) { + $form->assign($honorField, $honorValue); } } @@ -649,6 +597,83 @@ public static function pcpNotifyOwner(int $contributionID, array $contributionSo } } + /** + * @param int|null $profileID + * @param int|null $honorId + * @param array $params + * + * @return array + * @throws \CRM_Core_Exception + * @internal temporary function to retrieve template variables for honor profile. + * + */ + public static function getHonorTemplateVariables(?int $profileID, ?int $honorId, array $params): array { + $honoreeVariables = ['honoreeProfile' => NULL, 'honorName' => NULL]; + if (!$profileID) { + return $honoreeVariables; + } + $profileContactType = CRM_Core_BAO_UFGroup::getContactType($profileID); + $profileFields = CRM_Core_BAO_UFGroup::getFields($profileID); + $honoreeProfileFields = $values = []; + $honorName = NULL; + + if ($honorId) { + CRM_Core_BAO_UFGroup::getValues($honorId, $profileFields, $values, FALSE, $params); + if (empty($params)) { + foreach ($profileFields as $name => $field) { + $title = $field['title']; + $params[$field['name']] = $values[$title]; + } + } + } + + //remove name related fields and construct name string with prefix/suffix + //which will be later assigned to template + // This looks like a really drawn out way to get the display name... + switch ($profileContactType) { + case 'Individual': + if (array_key_exists('prefix_id', $params)) { + $honorName = CRM_Utils_Array::value($params['prefix_id'], + CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'prefix_id') + ); + unset($profileFields['prefix_id']); + } + $honorName .= ' ' . $params['first_name'] . ' ' . $params['last_name']; + unset($profileFields['first_name']); + unset($profileFields['last_name']); + if (array_key_exists('suffix_id', $params)) { + $honorName .= ' ' . CRM_Utils_Array::value($params['suffix_id'], + CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'suffix_id') + ); + unset($profileFields['suffix_id']); + } + break; + + case 'Organization': + $honorName = $params['organization_name']; + unset($profileFields['organization_name']); + break; + + case 'Household': + $honorName = $params['household_name']; + unset($profileFields['household_name']); + break; + } + + if ($honorId) { + $honoreeProfileFields['Name'] = $honorName; + foreach ($profileFields as $name => $field) { + $title = $field['title']; + $honoreeProfileFields[$title] = $values[$title]; + } + $honoreeVariables['honoreeProfile'] = $honoreeProfileFields; + } + else { + $honoreeVariables['honorName'] = $honorName; + } + return $honoreeVariables; + } + /** * @param string|null $entityName * @param int|null $userId diff --git a/www/modules/civicrm/CRM/Contribute/BAO/Premium.php b/www/modules/civicrm/CRM/Contribute/BAO/Premium.php index 349545ca5..d9218a030 100644 --- a/www/modules/civicrm/CRM/Contribute/BAO/Premium.php +++ b/www/modules/civicrm/CRM/Contribute/BAO/Premium.php @@ -130,12 +130,8 @@ public static function buildPremiumBlock(&$form, $pageID, $formItems = FALSE, $s CRM_Core_DAO::storeValues($productDAO, $products[$productDAO->id]); } } - $options = $temp = []; - $temp = explode(',', $productDAO->options); - foreach ($temp as $value) { - $options[trim($value)] = trim($value); - } - if ($temp[0] != '') { + $options = self::parseProductOptions($productDAO->options); + if (!empty($options)) { $form->addElement('select', 'options_' . $productDAO->id, NULL, $options); } } @@ -246,4 +242,25 @@ public static function getPremiumProductInfo() { return self::$productInfo; } + /** + * Convert key=val options into an array while keeping + * compatibility for values only. + */ + public static function parseProductOptions($string) : array { + $options = []; + $temp = explode(',', $string); + + foreach ($temp as $value) { + $parts = explode('=', $value, 2); + if (count($parts) == 2) { + $options[trim($parts[0])] = trim($parts[1]); + } + else { + $options[trim($value)] = trim($value); + } + } + + return $options; + } + } diff --git a/www/modules/civicrm/CRM/Contribute/DAO/Contribution.php b/www/modules/civicrm/CRM/Contribute/DAO/Contribution.php index af1c99d6d..e49d4127e 100644 --- a/www/modules/civicrm/CRM/Contribute/DAO/Contribution.php +++ b/www/modules/civicrm/CRM/Contribute/DAO/Contribution.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contribute/Contribution.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:ac06e44eef3c31fd91b5a2e722cce4e2) + * (GenCodeChecksum:8f56c256f38dc287fee1374252c97e1f) */ /** diff --git a/www/modules/civicrm/CRM/Contribute/DAO/ContributionPage.php b/www/modules/civicrm/CRM/Contribute/DAO/ContributionPage.php index da7d64498..f43b307e7 100644 --- a/www/modules/civicrm/CRM/Contribute/DAO/ContributionPage.php +++ b/www/modules/civicrm/CRM/Contribute/DAO/ContributionPage.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contribute/ContributionPage.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:0878c44e4ecbf95e1c3c18731633e8a1) + * (GenCodeChecksum:5bc752f7ab209ff0dcebdf3ff1f8af12) */ /** diff --git a/www/modules/civicrm/CRM/Contribute/DAO/ContributionProduct.php b/www/modules/civicrm/CRM/Contribute/DAO/ContributionProduct.php index 66a2b4e4c..1c8340c28 100644 --- a/www/modules/civicrm/CRM/Contribute/DAO/ContributionProduct.php +++ b/www/modules/civicrm/CRM/Contribute/DAO/ContributionProduct.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contribute/ContributionProduct.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:bdaea7c43da84af6dc29826b644505a0) + * (GenCodeChecksum:fbc39de2439ae647c6418d00d602e5eb) */ /** diff --git a/www/modules/civicrm/CRM/Contribute/DAO/ContributionRecur.php b/www/modules/civicrm/CRM/Contribute/DAO/ContributionRecur.php index 2efe3a32c..364a06f1b 100644 --- a/www/modules/civicrm/CRM/Contribute/DAO/ContributionRecur.php +++ b/www/modules/civicrm/CRM/Contribute/DAO/ContributionRecur.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contribute/ContributionRecur.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:5a9eadcb838cebf85ce424f126822690) + * (GenCodeChecksum:21e7fea5e149343a8b8f170d6d4c8bee) */ /** diff --git a/www/modules/civicrm/CRM/Contribute/DAO/ContributionSoft.php b/www/modules/civicrm/CRM/Contribute/DAO/ContributionSoft.php index c13707cb7..6e2f77655 100644 --- a/www/modules/civicrm/CRM/Contribute/DAO/ContributionSoft.php +++ b/www/modules/civicrm/CRM/Contribute/DAO/ContributionSoft.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contribute/ContributionSoft.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:3ecf6fc6d7483e6dea0d1fb222797cd7) + * (GenCodeChecksum:2fcf711173127e3422c59c96b21d639e) */ /** diff --git a/www/modules/civicrm/CRM/Contribute/DAO/Premium.php b/www/modules/civicrm/CRM/Contribute/DAO/Premium.php index 543c803b6..57ecf2221 100644 --- a/www/modules/civicrm/CRM/Contribute/DAO/Premium.php +++ b/www/modules/civicrm/CRM/Contribute/DAO/Premium.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contribute/Premium.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:7b586940572f3109ff5917254103dd4e) + * (GenCodeChecksum:4624fdb600c07353a4b769547d0ee99f) */ /** diff --git a/www/modules/civicrm/CRM/Contribute/DAO/PremiumsProduct.php b/www/modules/civicrm/CRM/Contribute/DAO/PremiumsProduct.php index 59bc02a59..557619b94 100644 --- a/www/modules/civicrm/CRM/Contribute/DAO/PremiumsProduct.php +++ b/www/modules/civicrm/CRM/Contribute/DAO/PremiumsProduct.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contribute/PremiumsProduct.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:6e4ab04c238e7960a2134aeda0df31f0) + * (GenCodeChecksum:e0fd905bafc0c4946615101587f696a8) */ /** diff --git a/www/modules/civicrm/CRM/Contribute/DAO/Product.php b/www/modules/civicrm/CRM/Contribute/DAO/Product.php index ea23b12cb..368b77c51 100644 --- a/www/modules/civicrm/CRM/Contribute/DAO/Product.php +++ b/www/modules/civicrm/CRM/Contribute/DAO/Product.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contribute/Product.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:f21f30ca57294d554e0b4446ef4d20c4) + * (GenCodeChecksum:25d230f83535ed383bf72377471e6d98) */ /** @@ -167,8 +167,7 @@ class CRM_Contribute_DAO_Product extends CRM_Core_DAO { public $is_active; /** - * Rolling means we set start/end based on current day, fixed means we set start/end for current year or month - * (e.g. 1 year + fixed -> we would set start/end for 1/1/06 thru 12/31/06 for any premium chosen in 2006) + * Rolling means we set start/end based on current day, fixed means we set start/end for current year or month (e.g. 1 year + fixed -> we would set start/end for 1/1/06 thru 12/31/06 for any premium chosen in 2006) * * @var string|null * (SQL type: varchar(8)) @@ -344,6 +343,7 @@ public static function &fields() { 'entity' => 'Product', 'bao' => 'CRM_Contribute_BAO_Product', 'localizable' => 1, + 'serialize' => self::SERIALIZE_COMMA, 'add' => '1.4', ], 'image' => [ @@ -541,8 +541,7 @@ public static function &fields() { 'name' => 'period_type', 'type' => CRM_Utils_Type::T_STRING, 'title' => ts('Period Type'), - 'description' => ts('Rolling means we set start/end based on current day, fixed means we set start/end for current year or month - (e.g. 1 year + fixed -> we would set start/end for 1/1/06 thru 12/31/06 for any premium chosen in 2006) '), + 'description' => ts('Rolling means we set start/end based on current day, fixed means we set start/end for current year or month (e.g. 1 year + fixed -> we would set start/end for 1/1/06 thru 12/31/06 for any premium chosen in 2006)'), 'maxlength' => 8, 'size' => CRM_Utils_Type::EIGHT, 'usage' => [ diff --git a/www/modules/civicrm/CRM/Contribute/DAO/Widget.php b/www/modules/civicrm/CRM/Contribute/DAO/Widget.php index f762f597b..7ccacac5e 100644 --- a/www/modules/civicrm/CRM/Contribute/DAO/Widget.php +++ b/www/modules/civicrm/CRM/Contribute/DAO/Widget.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Contribute/Widget.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:14b5198c0eb470500fe1c33d2a6526e4) + * (GenCodeChecksum:4d25bbc05ba61b22a8226f5173af4f90) */ /** diff --git a/www/modules/civicrm/CRM/Contribute/Form/AbstractEditPayment.php b/www/modules/civicrm/CRM/Contribute/Form/AbstractEditPayment.php index abe92ba10..045483de2 100644 --- a/www/modules/civicrm/CRM/Contribute/Form/AbstractEditPayment.php +++ b/www/modules/civicrm/CRM/Contribute/Form/AbstractEditPayment.php @@ -170,13 +170,15 @@ class CRM_Contribute_Form_AbstractEditPayment extends CRM_Contact_Form_Task { protected $contributionID; /** - * Get the contribution id that has been created or is being edited. + * Get the contribution ID. * - * @internal - not supported for outside core. + * @api This function will not change in a minor release and is supported for + * use outside of core. This annotation / external support for properties + * is only given where there is specific test cover. * * @return int|null */ - protected function getContributionID(): ?int { + public function getContributionID(): ?int { return $this->contributionID; } @@ -254,6 +256,10 @@ public function __get($name) { CRM_Core_Error::deprecatedWarning('attempt to access undefined property _params - use externally supported function getSubmittedValues()'); return $this->getSubmittedValues(); } + if ($name === '_lineItem') { + CRM_Core_Error::deprecatedWarning('attempt to access undefined property _params - use externally supported function getSubmittedValues()'); + return [0 => $this->getLineItems()]; + } CRM_Core_Error::deprecatedWarning('attempt to access invalid property :' . $name); } @@ -351,22 +357,6 @@ public function buildValuesAndAssignOnline_Note_Type($id, &$values) { } } - /** - * @param string $type - * Eg 'Contribution'. - * @param string $subType - * @param int $entityId - */ - public function applyCustomData($type, $subType, $entityId) { - $this->set('type', $type); - $this->set('subType', $subType); - $this->set('entityId', $entityId); - - CRM_Custom_Form_CustomData::preProcess($this, NULL, $subType, 1, $type, $entityId); - CRM_Custom_Form_CustomData::buildQuickForm($this); - CRM_Custom_Form_CustomData::setDefaultValues($this); - } - /** * @return array * Array of valid processors. The array resembles the DB table but also has 'object' as a key @@ -424,7 +414,7 @@ public function assignProcessors() { // this required to show billing block // @todo remove this assignment the billing block is now designed to be always included but will not show fieldsets unless those sets of fields are assigned - $this->assign_by_ref('paymentProcessor', $this->_paymentProcessor); + $this->assign('paymentProcessor', $this->_paymentProcessor); } /** @@ -666,21 +656,12 @@ protected function addPaymentProcessorSelect($isRequired, $isBuildRecurBlock = F /** * Assign the values to build the payment info block. - * - * @return string - * Block title. */ protected function assignPaymentInfoBlock() { $paymentInfo = CRM_Contribute_BAO_Contribution::getPaymentInfo($this->_id, $this->_component, TRUE); - $title = ts('View Payment'); - if (!empty($this->_component) && $this->_component == 'event') { - $info = CRM_Event_BAO_Participant::participantDetails($this->_id); - $title .= " - {$info['title']}"; - } $this->assign('transaction', TRUE); $this->assign('payments', $paymentInfo['transaction'] ?? NULL); $this->assign('paymentLinks', $paymentInfo['payment_links']); - return $title; } protected function assignContactEmailDetails(): void { diff --git a/www/modules/civicrm/CRM/Contribute/Form/AdditionalInfo.php b/www/modules/civicrm/CRM/Contribute/Form/AdditionalInfo.php index 6e418805d..bcd70b3ab 100644 --- a/www/modules/civicrm/CRM/Contribute/Form/AdditionalInfo.php +++ b/www/modules/civicrm/CRM/Contribute/Form/AdditionalInfo.php @@ -40,11 +40,9 @@ public static function buildPremium($form) { while ($dao->fetch()) { $sel1[$dao->id] = $dao->name . " ( " . $dao->sku . " )"; $min_amount[$dao->id] = $dao->min_contribution; - $options = explode(',', $dao->options); - foreach ($options as $k => $v) { - $options[$k] = trim($v); - } - if ($options[0] != '') { + $options = CRM_Contribute_BAO_Premium::parseProductOptions($dao->options); + if (!empty($options)) { + $options = ['' => ts('- select -')] + $options; $sel2[$dao->id] = $options; } $form->assign('premiums', TRUE); @@ -189,7 +187,7 @@ public static function processPremium($params, $contributionID, $premiumID = NUL CRM_Contribute_BAO_Product::retrieve($premiumParams, $productDetails); $dao->financial_type_id = $productDetails['financial_type_id'] ?? NULL; if (!empty($options[$selectedProductID])) { - $dao->product_option = $options[$selectedProductID][$selectedProductOptionID]; + $dao->product_option = $selectedProductOptionID; } // This IF condition codeblock does the following: @@ -239,6 +237,8 @@ public static function processPremium($params, $contributionID, $premiumID = NUL * @param int $contactID * @param int $contributionID * @param int $contributionNoteID + * + * @throws \CRM_Core_Exception */ public static function processNote($params, $contactID, $contributionID, $contributionNoteID = NULL) { if (CRM_Utils_System::isNull($params['note']) && $contributionNoteID) { @@ -322,7 +322,7 @@ public static function emailReceipt(&$form, &$params, $ccContribution = FALSE) { if (!empty($params['payment_instrument_id'])) { $paymentInstrument = CRM_Contribute_PseudoConstant::paymentInstrument(); $params['paidBy'] = $paymentInstrument[$params['payment_instrument_id']]; - if ($params['paidBy'] != 'Check' && isset($params['check_number'])) { + if ($params['paidBy'] !== 'Check' && isset($params['check_number'])) { unset($params['check_number']); } } @@ -421,7 +421,7 @@ public static function emailReceipt(&$form, &$params, $ccContribution = FALSE) { $form->assign('customGroup', $customGroup); } - $form->assign_by_ref('formValues', $params); + $form->assign('formValues', $params); list($contributorDisplayName, $contributorEmail ) = CRM_Contact_BAO_Contact_Location::getEmailDetails($params['contact_id']); diff --git a/www/modules/civicrm/CRM/Contribute/Form/AdditionalPayment.php b/www/modules/civicrm/CRM/Contribute/Form/AdditionalPayment.php index a846482e8..0fb4fc03b 100644 --- a/www/modules/civicrm/CRM/Contribute/Form/AdditionalPayment.php +++ b/www/modules/civicrm/CRM/Contribute/Form/AdditionalPayment.php @@ -24,6 +24,7 @@ class CRM_Contribute_Form_AdditionalPayment extends CRM_Contribute_Form_AbstractEditPayment { use CRM_Contact_Form_ContactFormTrait; use CRM_Contribute_Form_ContributeFormTrait; + use CRM_Event_Form_EventFormTrait; /** * Id of the component entity @@ -83,9 +84,17 @@ public function preProcess() { $this->assign('component', $this->_component); $this->assign('id', $this->_id); $this->assign('suppressPaymentFormButtons', $this->isBeingCalledFromSelectorContext()); - - if ($this->_view === 'transaction' && ($this->_action & CRM_Core_Action::BROWSE)) { - $title = $this->assignPaymentInfoBlock(); + $isShowPayments = $this->isViewMode(); + $this->assign('transaction', $isShowPayments); + if ($isShowPayments) { + $paymentInfo = CRM_Contribute_BAO_Contribution::getPaymentInfo($this->getContributionID(), 'contribution', TRUE); + $this->assign('payments', $paymentInfo['transaction'] ?? NULL); + $this->assign('paymentLinks', $paymentInfo['payment_links']); + $title = ts('View Payment'); + if (!empty($this->_component) && $this->_component === 'event') { + $info = CRM_Event_BAO_Participant::participantDetails($this->_id); + $title .= " - {$info['title']}"; + } $this->setTitle($title); return; } @@ -139,8 +148,8 @@ protected function isBeingCalledFromSelectorContext() { * @return array * @throws \CRM_Core_Exception */ - public function setDefaultValues() { - if ($this->_view === 'transaction' && ($this->_action & CRM_Core_Action::BROWSE)) { + public function setDefaultValues(): ?array { + if ($this->isViewMode()) { return NULL; } $defaults = []; @@ -215,16 +224,9 @@ public function buildQuickForm() { $this->add('textarea', 'receipt_text', ts('Confirmation Message')); $this->addField('trxn_date', ['entity' => 'FinancialTrxn', 'label' => $this->isARefund() ? ts('Refund Date') : ts('Contribution Date'), 'context' => 'Contribution'], FALSE, FALSE); + $this->assign('eventName', $this->getEventValue('title')); - if ($this->_contactId && $this->_id) { - if ($this->_component === 'event') { - $eventId = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Participant', $this->_id, 'event_id', 'id'); - $event = CRM_Event_BAO_Event::getEvents(0, $eventId); - $this->assign('eventName', $event[$eventId]); - } - } - - $this->assign('displayName', $this->_contributorDisplayName); + $this->assign('displayName', $this->getContactValue('display_name')); $this->assign('component', $this->_component); $this->assign('email', $this->_contributorEmail); @@ -295,13 +297,9 @@ public static function formRule($fields, $files, $self) { public function postProcess() { $submittedValues = $this->controller->exportValues($this->_name); $this->submit($submittedValues); - $childTab = 'contribute'; - if ($this->_component === 'event') { - $childTab = 'participant'; - } $session = CRM_Core_Session::singleton(); $session->replaceUserContext(CRM_Utils_System::url('civicrm/contact/view', - "reset=1&cid={$this->_contactId}&selectedChild={$childTab}" + "reset=1&cid={$this->_contactId}&selectedChild=" . $this->getParticipantID() ? 'participant' : 'contribute' )); } @@ -319,7 +317,6 @@ public function submit($submittedValues) { ($this->_mode === 'test') ); } - $this->assign('displayName', $this->getContactValue('display_name')); $paymentResult = []; if ($this->_mode) { @@ -335,10 +332,10 @@ public function submit($submittedValues) { 'contribution_id' => $this->getContributionID(), 'payment_processor_id' => $this->getPaymentProcessorID(), 'card_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_FinancialTrxn', 'card_type_id', $this->getSubmittedValue('credit_card_type')), - 'pan_truncation' => substr((string) $this->getSubmittedValue('credit_card_number'), -4), + 'pan_truncation' => $this->getSubmittedValue('pan_truncation') ?: substr((string) $this->getSubmittedValue('credit_card_number'), -4), 'trxn_result_code' => $paymentResult['trxn_result_code'] ?? NULL, 'payment_instrument_id' => $this->getSubmittedValue('payment_instrument_id'), - 'trxn_id' => $paymentResult['trxn_id'] ?? NULL, + 'trxn_id' => $paymentResult['trxn_id'] ?? ($this->getSubmittedValue('trxn_id') ?? NULL), 'trxn_date' => $this->getSubmittedValue('trxn_date'), // This form sends payment notification only, for historical reasons. 'is_send_contribution_notification' => FALSE, @@ -381,7 +378,6 @@ public function processCreditCard(): ?array { // we need to retrieve email address if ($this->_context === 'standalone' && $this->getSubmittedValue('is_email_receipt')) { [$displayName] = CRM_Contact_BAO_Contact_Location::getEmailDetails($this->_contactId); - $this->assign('displayName', $displayName); } // at this point we've created a contact and stored its address etc @@ -539,6 +535,23 @@ public function getContactID(): int { return (int) $this->_contactID; } + /** + * Get the relevant participant ID, if any, in use. + * + * @return int + * + * @api supported for external use. + * + * @noinspection PhpDocMissingThrowsInspection + * @noinspection PhpUnhandledExceptionInspection + */ + public function getParticipantID(): ?int { + if (CRM_Utils_Request::retrieve('component', 'String', $this, FALSE, 'contribution') !== 'event') { + return NULL; + } + return (int) CRM_Utils_Request::retrieve('id', 'Positive', $this, TRUE); + } + /** * Get the payment processor ID. * @@ -548,4 +561,12 @@ public function getPaymentProcessorID(): int { return (int) ($this->getSubmittedValue('payment_processor_id') ?: $this->_paymentProcessor['id']); } + /** + * @return bool + */ + public function isViewMode(): bool { + $isShowPayments = $this->_view === 'transaction' && ($this->_action & CRM_Core_Action::BROWSE); + return $isShowPayments; + } + } diff --git a/www/modules/civicrm/CRM/Contribute/Form/CancelSubscription.php b/www/modules/civicrm/CRM/Contribute/Form/CancelSubscription.php index 450132d24..4653532fb 100644 --- a/www/modules/civicrm/CRM/Contribute/Form/CancelSubscription.php +++ b/www/modules/civicrm/CRM/Contribute/Form/CancelSubscription.php @@ -66,20 +66,13 @@ public function preProcess() { 'selfService' => $this->isSelfService(), ]; - if ($this->_crid) { - // Are we cancelling a recurring contribution that is linked to an auto-renew membership? - if ($this->getSubscriptionDetails()->membership_id) { - $this->_mid = $this->getSubscriptionDetails()->membership_id; - } - } - - if ($this->_mid) { + if ($this->getMembershipID()) { $this->_mode = 'auto_renew'; // CRM-18468: crid is more accurate than mid for getting // subscriptionDetails, so don't get them again. $membershipTypes = CRM_Member_PseudoConstant::membershipType(); - $membershipTypeId = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership', $this->_mid, 'membership_type_id'); + $membershipTypeId = $this->getMembershipValue('membership_type_id'); $membershipType = $membershipTypes[$membershipTypeId] ?? ''; $this->assign('membershipType', $membershipType); $cancelRecurTextParams['membershipType'] = $membershipType; @@ -92,10 +85,7 @@ public function preProcess() { $this->_paymentProcessorObj = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->_coid, 'contribute', 'obj'); } - if ( - (!$this->_crid && !$this->_coid && !$this->_mid) || - (!$this->getSubscriptionDetails()) - ) { + if (!$this->getSubscriptionDetails()) { CRM_Core_Error::statusBounce(ts('Required information missing.')); } @@ -104,7 +94,7 @@ public function preProcess() { // handle context redirection CRM_Contribute_BAO_ContributionRecur::setSubscriptionContext(); - $this->setTitle($this->_mid ? ts('Cancel Auto-renewal') : ts('Cancel Recurring Contribution')); + $this->setTitle($this->getMembershipID() ? ts('Cancel Auto-renewal') : ts('Cancel Recurring Contribution')); $this->assign('mode', $this->_mode); if ($this->isSelfService() || !$this->_paymentProcessorObj->supports('cancelRecurring')) { @@ -160,7 +150,7 @@ public function buildQuickForm() { if (!empty($this->_donorEmail)) { $this->add('checkbox', 'is_notify', ts('Notify Contributor?') . " ({$this->_donorEmail})"); } - if ($this->_mid) { + if ($this->getMembershipID()) { $cancelButton = ts('Cancel Automatic Membership Renewal'); } else { @@ -234,16 +224,16 @@ public function postProcess() { try { civicrm_api3('ContributionRecur', 'cancel', [ 'id' => $this->getSubscriptionDetails()->recur_id, - 'membership_id' => $this->_mid, + 'membership_id' => $this->getMembershipID(), 'processor_message' => $message, 'cancel_reason' => $this->getSubmittedValue('cancel_reason'), ]); $tplParams = []; - if ($this->_mid) { - $inputParams = ['id' => $this->_mid]; + if ($this->getMembershipID()) { + $inputParams = ['id' => $this->getMembershipID()]; CRM_Member_BAO_Membership::getValues($inputParams, $tplParams); - $tplParams = $tplParams[$this->_mid]; + $tplParams = $tplParams[$this->getMembershipID()]; $tplParams['membership_status'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipStatus', $tplParams['status_id']); $tplParams['membershipType'] diff --git a/www/modules/civicrm/CRM/Contribute/Form/ContributeFormTrait.php b/www/modules/civicrm/CRM/Contribute/Form/ContributeFormTrait.php index 56ff4b2ef..e7e574756 100644 --- a/www/modules/civicrm/CRM/Contribute/Form/ContributeFormTrait.php +++ b/www/modules/civicrm/CRM/Contribute/Form/ContributeFormTrait.php @@ -115,7 +115,7 @@ public function getContributionRecurValue(string $fieldName) { } /** - * Get the selected Contribution ID. + * Get the selected Contribution Recur ID. * * @api This function will not change in a minor release and is supported for * use outside of core. This annotation / external support for properties @@ -124,7 +124,7 @@ public function getContributionRecurValue(string $fieldName) { * @noinspection PhpUnhandledExceptionInspection */ public function getContributionRecurID(): ?int { - throw new CRM_Core_Exception('`getContributionID` must be implemented'); + throw new CRM_Core_Exception('`getContributionRecurID` must be implemented'); } } diff --git a/www/modules/civicrm/CRM/Contribute/Form/Contribution.php b/www/modules/civicrm/CRM/Contribute/Form/Contribution.php index 2815e7933..1062fc3a1 100644 --- a/www/modules/civicrm/CRM/Contribute/Form/Contribution.php +++ b/www/modules/civicrm/CRM/Contribute/Form/Contribution.php @@ -20,6 +20,7 @@ class CRM_Contribute_Form_Contribution extends CRM_Contribute_Form_AbstractEditP use CRM_Contact_Form_ContactFormTrait; use CRM_Contribute_Form_ContributeFormTrait; use CRM_Financial_Form_PaymentProcessorFormTrait; + use CRM_Custom_Form_CustomDataTrait; /** * The id of the contribution that we are processing. @@ -159,6 +160,7 @@ class CRM_Contribute_Form_Contribution extends CRM_Contribute_Form_AbstractEditP /** * Price set as an array + * * @var array */ public $_priceSet; @@ -213,6 +215,8 @@ class CRM_Contribute_Form_Contribution extends CRM_Contribute_Form_AbstractEditP */ private $_payNow; + private $order; + /** * Explicitly declare the form context. */ @@ -302,9 +306,15 @@ public function preProcess(): void { } $this->assign('is_template', $this->_values['is_template']); - // when custom data is included in this page - if (!empty($_POST['hidden_custom'])) { - $this->applyCustomData('Contribution', $this->getFinancialTypeID(), $this->_id); + if ($this->isSubmitted()) { + // The custom data fields are added to the form by an ajax form. + // However, if they are not present in the element index they will + // not be available from `$this->getSubmittedValue()` in post process. + // We do not have to set defaults or otherwise render - just add to the element index. + $this->addCustomDataFieldsToForm('Contribution', array_filter([ + 'id' => $this->getContributionID(), + 'financial_type_id' => $this->getFinancialTypeID(), + ])); } $this->_lineItems = []; @@ -490,9 +500,13 @@ public function setDefaultValues() { if (empty($defaults['payment_instrument_id'])) { $defaults['payment_instrument_id'] = $this->getDefaultPaymentInstrumentId(); } - $this->assign('is_test', !empty($defaults['is_test'])); + $this->assign('is_test', !empty($defaults['is_test'])); + $this->assign('email', $this->getContactValue('email_primary.email')); + $this->assign('is_pay_later', !empty($defaults['is_pay_later'])); + $this->assign('contribution_status_id', $defaults['contribution_status_id'] ?? NULL); $this->assign('showOption', TRUE); + // For Premium section. if ($this->_premiumID) { $this->assign('showOption', FALSE); @@ -500,9 +514,8 @@ public function setDefaultValues() { if (!$options) { $this->assign('showOption', TRUE); } - $options_key = CRM_Utils_Array::key($this->_productDAO->product_option, $options); - if ($options_key) { - $defaults['product_name'] = [$this->_productDAO->product_id, trim($options_key)]; + if ($this->_productDAO->product_option) { + $defaults['product_name'] = [$this->_productDAO->product_id, $this->_productDAO->product_option]; } else { $defaults['product_name'] = [$this->_productDAO->product_id]; @@ -512,9 +525,6 @@ public function setDefaultValues() { } } - $this->assign('is_pay_later', !empty($defaults['is_pay_later'])); - - $this->assign('contribution_status_id', CRM_Utils_Array::value('contribution_status_id', $defaults)); if (!empty($defaults['contribution_status_id']) && in_array( CRM_Contribute_PseudoConstant::contributionStatus($defaults['contribution_status_id'], 'name'), // Historically not 'Cancelled' hence not using CRM_Contribute_BAO_Contribution::isContributionStatusNegative. @@ -542,8 +552,8 @@ public function setDefaultValues() { $this->assign('currency', $currency); // Hack to get currency info to the js layer. CRM-11440. CRM_Utils_Money::format(1); - $this->assign('currencySymbol', CRM_Utils_Array::value($currency, CRM_Utils_Money::$_currencySymbols)); - $this->assign('totalAmount', CRM_Utils_Array::value('total_amount', $defaults)); + $this->assign('currencySymbol', CRM_Utils_Money::$_currencySymbols[$currency] ?? NULL); + $this->assign('totalAmount', $defaults['total_amount'] ?? NULL); // Inherit campaign from pledge. if ($this->_ppID && !empty($this->_pledgeValues['campaign_id'])) { @@ -687,8 +697,7 @@ public function buildQuickForm() { $this->applyFilter('__ALL__', 'trim'); - //need to assign custom data type and subtype to the template - $this->assign('customDataType', 'Contribution'); + //need to assign custom data subtype to the template for initial custom data load $this->assign('customDataSubType', $this->getFinancialTypeID()); $this->assign('entityID', $this->getContributionID()); $this->assign('email', $this->getContactValue('email_primary.email')); @@ -924,32 +933,45 @@ public function buildQuickForm() { } /** - * Build the price set form. + * @throws \CRM_Core_Exception */ - private function buildPriceSet(): void { - $priceSetId = $this->getPriceSetID(); - $form = $this; - $component = 'contribution'; - $priceSet = CRM_Price_BAO_PriceSet::getSetDetail($priceSetId, TRUE, FALSE); - $form->_priceSet = $priceSet[$priceSetId] ?? NULL; - $validPriceFieldIds = array_keys($form->_priceSet['fields']); - - $form->_priceSet['id'] ??= $priceSetId; - $form->assign('priceSet', $form->_priceSet); + protected function getOrder(): CRM_Financial_BAO_Order { + if (!$this->order) { + $this->initializeOrder(); + } + return $this->order; + } - $feeBlock = &$form->_priceSet['fields']; + /** + * @throws \CRM_Core_Exception + */ + protected function initializeOrder(): void { + $this->order = new CRM_Financial_BAO_Order(); + $this->order->setPriceSetID($this->getPriceSetID()); + $this->order->setForm($this); + $this->order->setPriceSelectionFromUnfilteredInput($this->getSubmittedValues()); + } - // Call the buildAmount hook. - CRM_Utils_Hook::buildAmount($component ?? 'contribution', $form, $feeBlock); + /** + * Get the form context. + * + * This is important for passing to the buildAmount hook as CiviDiscount checks it. + * + * @return string + */ + public function getFormContext(): string { + return 'contribution'; + } - $hideAdminValues = !CRM_Core_Permission::check('edit contributions'); - // CRM-14492 Admin price fields should show up on event registration if user has 'administer CiviCRM' permissions - $adminFieldVisible = CRM_Core_Permission::check('administer CiviCRM'); - $checklifetime = FALSE; - foreach ($feeBlock as $id => $field) { + /** + * Build the price set form. + */ + private function buildPriceSet(): void { + $form = $this; + $this->_priceSet = $this->getOrder()->getPriceSetMetadata(); + foreach ($this->getPriceFieldMetaData() as $id => $field) { $options = $field['options'] ?? NULL; - - if (!is_array($options) || !in_array($id, $validPriceFieldIds)) { + if (!is_array($options)) { continue; } @@ -964,6 +986,29 @@ private function buildPriceSet(): void { ); } } + $form->assign('priceSet', $form->_priceSet); + } + + /** + * Get price field metadata. + * + * The returned value is an array of arrays where each array + * is an id-keyed price field and an 'options' key has been added to that + * array for any options. + * + * @api This function will not change in a minor release and is supported for + * use outside of core. This annotation / external support for properties + * is only given where there is specific test cover. + * + * @return array + */ + public function getPriceFieldMetaData(): array { + if (!empty($this->_priceSet['fields'])) { + return $this->_priceSet['fields']; + } + + $this->_priceSet['fields'] = $this->getOrder()->getPriceFieldsMetadata(); + return $this->_priceSet['fields']; } /** @@ -1194,7 +1239,7 @@ protected function processCreditCard($submittedValues, $lineItem, $contactID) { // so we copy stuff over to first_name etc. $paymentParams = $this->_params; $paymentParams['contactID'] = $contactID; - CRM_Core_Payment_Form::mapParams($this->_bltID, $this->_params, $paymentParams, TRUE); + CRM_Core_Payment_Form::mapParams(NULL, $this->_params, $paymentParams, TRUE); $financialType = new CRM_Financial_DAO_FinancialType(); $financialType->id = $params['financial_type_id']; @@ -1239,7 +1284,7 @@ protected function processCreditCard($submittedValues, $lineItem, $contactID) { 'is_test' => $isTest, 'campaign_id' => $this->_params['campaign_id'] ?? NULL, 'contribution_page_id' => $this->_params['contribution_page_id'] ?? NULL, - 'source' => CRM_Utils_Array::value('source', $paymentParams, CRM_Utils_Array::value('description', $paymentParams)), + 'source' => $paymentParams['source'] ?? $paymentParams['description'] ?? NULL, 'thankyou_date' => $this->_params['thankyou_date'] ?? NULL, ]; $contributionParams['payment_instrument_id'] = $this->_paymentProcessor['payment_instrument_id']; @@ -1973,20 +2018,23 @@ protected function submit($submittedValues, $action, $pledgePaymentID) { $params[$f] = $formValues[$f] ?? NULL; } if ($this->_id && $action & CRM_Core_Action::UPDATE) { + // @todo - should we remove all this - if it's going from Pending to Completed then + // add payment handles that - what statuses CAN be changed here? + // Also - the changing of is_pay_later to 0 here has been debated at times + // as it could be argued it still showed the intent. // Can only be updated to contribution which is handled via Payment.create $params['contribution_status_id'] = $this->getSubmittedValue('contribution_status_id'); - - // Set is_pay_later flag for back-office offline Pending status contributions CRM-8996 - // else if contribution_status is changed to Completed is_pay_later flag is changed to 0, CRM-15041 - if ($params['contribution_status_id'] == CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending')) { - $params['is_pay_later'] = 1; - } - elseif ($params['contribution_status_id'] == CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed')) { + if ($params['contribution_status_id'] == CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed')) { // @todo - if the contribution is new then it should be Pending status & then we use // Payment.create to update to Completed. + // If contribution_status is changed to Completed is_pay_later flag is changed to 0, CRM-15041 $params['is_pay_later'] = 0; } } + // Set is_pay_later flag for new back-office offline Pending status contributions CRM-8996 + if (!$this->getContributionID() && $params['contribution_status_id'] == CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending')) { + $params['is_pay_later'] = 1; + } $params['revenue_recognition_date'] = NULL; if (!empty($formValues['revenue_recognition_date'])) { @@ -2016,7 +2064,7 @@ protected function submit($submittedValues, $action, $pledgePaymentID) { } $params['line_item'] = $lineItem; $params['payment_processor_id'] = $params['payment_processor'] = $this->_paymentProcessor['id'] ?? NULL; - $params['tax_amount'] = CRM_Utils_Array::value('tax_amount', $submittedValues, CRM_Utils_Array::value('tax_amount', $this->_values)); + $params['tax_amount'] = $submittedValues['tax_amount'] ?? $this->_values['tax_amount'] ?? NULL; //create contribution. if ($isQuickConfig) { $params['is_quick_config'] = 1; @@ -2144,7 +2192,7 @@ protected function invoicingPostProcessHook($submittedValues, $action, $lineItem $this->assign('getTaxDetails', $getTaxDetails); } else { - $this->assign('totalTaxAmount', CRM_Utils_Array::value('tax_amount', $submittedValues)); + $this->assign('totalTaxAmount', $submittedValues['tax_amount'] ?? NULL); } } } diff --git a/www/modules/civicrm/CRM/Contribute/Form/Contribution/Confirm.php b/www/modules/civicrm/CRM/Contribute/Form/Contribution/Confirm.php index 1f60ac83b..bf123d2a4 100644 --- a/www/modules/civicrm/CRM/Contribute/Form/Contribution/Confirm.php +++ b/www/modules/civicrm/CRM/Contribute/Form/Contribution/Confirm.php @@ -287,7 +287,7 @@ public function preProcess() { // We may have fetched some billing details from the getPreApprovalDetails function so we // want to ensure we set this after that function has been called. - CRM_Core_Payment_Form::mapParams($this->_bltID, $preApprovalParams, $this->_params, FALSE); + CRM_Core_Payment_Form::mapParams(NULL, $preApprovalParams, $this->_params, FALSE); } $this->_params['is_pay_later'] = $this->get('is_pay_later'); @@ -970,8 +970,6 @@ protected function postProcessPremium($premiumParams, $contribution) { * - thankyou_date (not all forms will set this) * * @param CRM_Financial_DAO_FinancialType $financialType - * @param int $billingLocationID - * ID of billing location type. * @param bool $isRecur * Is this recurring? * @@ -987,7 +985,6 @@ protected function processFormContribution( $result, $contributionParams, $financialType, - $billingLocationID, $isRecur ) { $form = $this; @@ -1556,7 +1553,7 @@ protected function postProcessMembership( $paymentParams = array_merge($this->_params, ['contributionID' => $this->_values['contribution_other_id']]); // CRM-19792 : set necessary fields for payment processor - CRM_Core_Payment_Form::mapParams($this->_bltID, $paymentParams, $paymentParams, TRUE); + CRM_Core_Payment_Form::mapParams(NULL, $paymentParams, $paymentParams, TRUE); // If this is a single membership-related contribution, it won't have // be performed yet, so do it now. @@ -1713,14 +1710,13 @@ private function processSecondaryFinancialTransaction($contactID, $tempParams, $ } // CRM-19792 : set necessary fields for payment processor - CRM_Core_Payment_Form::mapParams($this->_bltID, $this->_params, $tempParams, TRUE); + CRM_Core_Payment_Form::mapParams(NULL, $this->_params, $tempParams, TRUE); $membershipContribution = $this->processFormContribution( $tempParams, $tempParams, $contributionParams, $financialType, - $this->_bltID, $isRecur ); @@ -2327,7 +2323,7 @@ protected function doMembershipProcessing($contactID, $membershipParams, $premiu $membershipParams['campaign_id'] = $this->_values['campaign_id'] ?? NULL; } - $this->_params = CRM_Core_Payment_Form::mapParams($this->_bltID, $this->_params, $membershipParams, TRUE); + $this->_params = CRM_Core_Payment_Form::mapParams(NULL, $this->_params, $membershipParams, TRUE); // This could be set by a hook. if (!empty($this->_params['installments'])) { @@ -2384,7 +2380,7 @@ protected function doMembershipProcessing($contactID, $membershipParams, $premiu } catch (CRM_Core_Exception $e) { CRM_Core_Session::singleton()->setStatus($e->getMessage()); - CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contribute/transact', "_qf_Main_display=true&qfKey={$this->_params['qfKey']}")); + CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contribute/transact', "_qf_Main_display=true&qfKey=" . ($this->_params['qfKey'] ?? ''))); } if (!$this->_amount > 0.0 || !$membershipParams['amount']) { // we need to explicitly create a CMS user in case of free memberships @@ -2441,12 +2437,12 @@ protected function completeTransaction($result, $contributionID) { * * @param string $message */ - protected function bounceOnError($message) { + protected function bounceOnError($message): void { CRM_Core_Session::singleton() - ->setStatus(ts("Payment Processor Error message :") . + ->setStatus(ts('Payment Processor Error message :') . $message); CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contribute/transact', - "_qf_Main_display=true&qfKey={$this->_params['qfKey']}" + '_qf_Main_display=true&qfKey=' . ($this->_params['qfKey'] ?? NULL) )); } @@ -2489,7 +2485,7 @@ public function processConfirm( $isRecur ): array { $form = $this; - CRM_Core_Payment_Form::mapParams($this->_bltID, $form->_params, $paymentParams, TRUE); + CRM_Core_Payment_Form::mapParams(NULL, $form->_params, $paymentParams, TRUE); $isPaymentTransaction = self::isPaymentTransaction($this); $financialType = new CRM_Financial_DAO_FinancialType(); @@ -2526,13 +2522,13 @@ public function processConfirm( 'id' => $paymentParams['contribution_id'] ?? NULL, 'contact_id' => $contactID, 'is_test' => $isTest, - 'source' => CRM_Utils_Array::value('source', $paymentParams, CRM_Utils_Array::value('description', $paymentParams)), + 'source' => $paymentParams['source'] ?? $paymentParams['description'] ?? NULL, ]; // CRM-21200: Don't overwrite contribution details during 'Pay now' payment if (empty($form->_params['contribution_id'])) { $contributionParams['contribution_page_id'] = $form->_id; - $contributionParams['campaign_id'] = CRM_Utils_Array::value('campaign_id', $paymentParams, CRM_Utils_Array::value('campaign_id', $form->_values)); + $contributionParams['campaign_id'] = $paymentParams['campaign_id'] ?? $form->_values['campaign_id'] ?? NULL; } // In case of 'Pay now' payment, append the contribution source with new text 'Paid later via page ID: N.' else { @@ -2559,7 +2555,6 @@ public function processConfirm( NULL, $contributionParams, $financialType, - $form->_bltID, $isRecur ); // CRM-13074 - create the CMSUser after the transaction is completed as it @@ -2929,7 +2924,7 @@ protected function getLineItemsForMembershipCreate(int $membershipTypeID, int $d if (empty($lineItem['membership_type_id']) && $this->isSeparateMembershipPayment()) { continue; } - $lineItemSplit[$lineItem['membership_type_id'] ?: $defaultMembershipTypeID][$lineItem['price_field_id']] = $lineItem; + $lineItemSplit[$lineItem['membership_type_id'] ?: $defaultMembershipTypeID]['price_field_value_' . $lineItem['price_field_value_id']] = $lineItem; } return $lineItemSplit[$membershipTypeID]; } diff --git a/www/modules/civicrm/CRM/Contribute/Form/Contribution/Main.php b/www/modules/civicrm/CRM/Contribute/Form/Contribution/Main.php index bbdb41256..de67551a2 100644 --- a/www/modules/civicrm/CRM/Contribute/Form/Contribution/Main.php +++ b/www/modules/civicrm/CRM/Contribute/Form/Contribution/Main.php @@ -125,8 +125,8 @@ public function preProcess() { // Make the contributionPageID available to the template $this->assign('contributionPageID', $this->_id); $this->assign('ccid', $this->_ccid); - $this->assign('isShare', CRM_Utils_Array::value('is_share', $this->_values)); - $this->assign('isConfirmEnabled', CRM_Utils_Array::value('is_confirm_enabled', $this->_values)); + $this->assign('isShare', $this->_values['is_share'] ?? NULL); + $this->assign('isConfirmEnabled', $this->_values['is_confirm_enabled'] ?? NULL); // Required for currency formatting in the JS layer // this is a temporary fix intended to resolve a regression quickly @@ -243,13 +243,14 @@ public function setDefaultValues() { $memtypeID = NULL; if ($this->_priceSetId) { if ($this->getFormContext() === 'membership') { + $existingMembershipTypeID = $this->getRenewableMembershipValue('membership_type_id'); $selectedCurrentMemTypes = []; foreach ($this->_priceSet['fields'] as $key => $val) { - foreach ($val['options'] as $keys => $values) { - $opMemTypeId = $values['membership_type_id'] ?? NULL; - $priceFieldName = 'price_' . $values['price_field_id']; + foreach ($val['options'] as $keys => $priceFieldOption) { + $opMemTypeId = $priceFieldOption['membership_type_id'] ?? NULL; + $priceFieldName = 'price_' . $priceFieldOption['price_field_id']; $priceFieldValue = CRM_Price_BAO_PriceSet::getPriceFieldValueFromURL($this, $priceFieldName); - if (!empty($priceFieldValue) && !$this->isDefined('RenewableMembership')) { + if (!empty($priceFieldValue)) { CRM_Price_BAO_PriceSet::setDefaultPriceSetField($priceFieldName, $priceFieldValue, $val['html_type'], $this->_defaults); // break here to prevent overwriting of default due to 'is_default' // option configuration or setting of current membership or @@ -257,23 +258,20 @@ public function setDefaultValues() { // The value sent via URL get's higher priority. break; } - if ($opMemTypeId && - // @todo - maybe use the defined renewable membership to avoid lifetime memberships. - !empty($this->getExistingMembership($opMemTypeId)) && - !in_array($opMemTypeId, $selectedCurrentMemTypes) + if ($existingMembershipTypeID && $existingMembershipTypeID === $priceFieldOption['membership_type_id'] + && !in_array($opMemTypeId, $selectedCurrentMemTypes) ) { CRM_Price_BAO_PriceSet::setDefaultPriceSetField($priceFieldName, $keys, $val['html_type'], $this->_defaults); - $memtypeID = $selectedCurrentMemTypes[] = $values['membership_type_id']; + $memtypeID = $selectedCurrentMemTypes[] = $priceFieldOption['membership_type_id']; } - elseif (!empty($values['is_default']) && !$opMemTypeId && (!isset($this->_defaults[$priceFieldName]) || + elseif (!empty($priceFieldOption['is_default']) && (!isset($this->_defaults[$priceFieldName]) || ($val['html_type'] === 'CheckBox' && !isset($this->_defaults[$priceFieldName][$keys])))) { CRM_Price_BAO_PriceSet::setDefaultPriceSetField($priceFieldName, $keys, $val['html_type'], $this->_defaults); - $memtypeID = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $this->_defaults[$priceFieldName], 'membership_type_id'); } } } - $membershipID = CRM_Utils_Array::value('id', CRM_Member_BAO_Membership::getContactMembership($contactID, $memtypeID, NULL)); - if ($contactID) { + + if ($contactID && $existingMembershipTypeID) { // Set the default values for any membership custom fields on the page via a profile. // Note that this will have been done further up if the contact ID was not determined. foreach ($this->_fields as $name => $field) { @@ -283,7 +281,7 @@ public function setDefaultValues() { if (!CRM_Core_BAO_CustomGroup::checkCustomField($customFieldID, ['Membership']) ) { CRM_Core_BAO_CustomField::setProfileDefaults($customFieldID, $name, $this->_defaults, - $membershipID, CRM_Profile_Form::MODE_REGISTER + $existingMembershipTypeID, CRM_Profile_Form::MODE_REGISTER ); } } @@ -342,7 +340,7 @@ public function buildQuickForm() { } $this->applyFilter('__ALL__', 'trim'); - $this->assign('showMainEmail', empty($this->_ccid)); + $this->assign('showMainEmail', (empty($this->_ccid) && $this->_emailExists === FALSE)); if (empty($this->_ccid)) { if ($this->_emailExists == FALSE) { $this->add('text', "email-{$this->_bltID}", @@ -816,13 +814,16 @@ private function buildRecur(): void { * true if no errors, else array of errors */ public static function formRule($fields, $files, $self) { + foreach ($fields as $key => $field) { + $fields[$key] = $self->getUnLocalizedSubmittedValue($key, $field); + } $self->resetOrder($fields); $errors = array_filter(['auto_renew' => $self->getAutoRenewError($fields)]); // @todo - should just be $this->getOrder()->getTotalAmount() $amount = $self->computeAmount($fields, $self->_values); if ((!empty($fields['selectMembership']) && - $fields['selectMembership'] != 'no_thanks' + $fields['selectMembership'] !== 'no_thanks' ) || (!empty($fields['priceSetId']) && $self->_useForMember @@ -864,7 +865,7 @@ public static function formRule($fields, $files, $self) { $otherAmount = $priceField->id; } elseif (!empty($fields["price_{$priceField->id}"])) { - $otherAmountVal = CRM_Utils_Rule::cleanMoney($fields["price_{$priceField->id}"]); + $otherAmountVal = $fields["price_{$priceField->id}"]; $min = $self->_values['min_amount'] ?? NULL; $max = $self->_values['max_amount'] ?? NULL; if ($min && $otherAmountVal < $min) { @@ -1133,21 +1134,20 @@ public static function formRule($fields, $files, $self) { */ private function computeAmount($params, $formValues) { $amount = 0; - // First clean up the other amount field if present. - if (isset($params['amount_other'])) { - $params['amount_other'] = CRM_Utils_Rule::cleanMoney($params['amount_other']); - } if (($params['amount'] ?? NULL) == 'amount_other_radio' || !empty($params['amount_other'])) { + // @todo - probably unreachable - field would be (e.) price_12 now.... $amount = $params['amount_other']; } elseif (!empty($params['pledge_amount'])) { foreach ($params['pledge_amount'] as $paymentId => $dontCare) { + // @todo - why would this be a good thing? Is it reachable. $amount += CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_PledgePayment', $paymentId, 'scheduled_amount'); } } else { if (!empty($formValues['amount'])) { + // @todo - probably unreachable. $amountID = $params['amount'] ?? NULL; if ($amountID) { @@ -1317,8 +1317,7 @@ protected function skipToThankYouPage() { // build the confirm page $confirmForm = &$this->controller->_pages['Confirm']; - $confirmForm->preProcess(); - $confirmForm->buildQuickForm(); + $confirmForm->buildForm(); // the confirmation page is valid $data = &$this->controller->container(); @@ -1667,21 +1666,21 @@ private function buildPledgeBlock() { if (!empty($overduePayments)) { foreach ($overduePayments as $id => $payment) { $label = ts("%1 - due on %2 (overdue)", [ - 1 => CRM_Utils_Money::format(CRM_Utils_Array::value('scheduled_amount', $payment), CRM_Utils_Array::value('scheduled_amount_currency', $payment)), + 1 => CRM_Utils_Money::format($payment['scheduled_amount'] ?? NULL, $payment['scheduled_amount_currency'] ?? NULL), 2 => $payment['scheduled_date'] ?? NULL, ]); $paymentID = $payment['id'] ?? NULL; - $payments[] = $this->createElement('checkbox', $paymentID, NULL, $label, ['amount' => CRM_Utils_Array::value('scheduled_amount', $payment)]); + $payments[] = $this->createElement('checkbox', $paymentID, NULL, $label, ['amount' => $payment['scheduled_amount'] ?? NULL]); } } if (!empty($nextPayment)) { $label = ts("%1 - due on %2", [ - 1 => CRM_Utils_Money::format(CRM_Utils_Array::value('scheduled_amount', $nextPayment), CRM_Utils_Array::value('scheduled_amount_currency', $nextPayment)), + 1 => CRM_Utils_Money::format($nextPayment['scheduled_amount'] ?? NULL, $nextPayment['scheduled_amount_currency'] ?? NULL), 2 => $nextPayment['scheduled_date'] ?? NULL, ]); $paymentID = $nextPayment['id'] ?? NULL; - $payments[] = $this->createElement('checkbox', $paymentID, NULL, $label, ['amount' => CRM_Utils_Array::value('scheduled_amount', $nextPayment)]); + $payments[] = $this->createElement('checkbox', $paymentID, NULL, $label, ['amount' => $nextPayment['scheduled_amount'] ?? NULL]); } // give error if empty or build form for payment. if (empty($payments)) { @@ -1983,4 +1982,17 @@ protected function resetOrder(array $fields, bool $sanitized = TRUE): void { $this->order->recalculateLineItems(); } + /** + * @param string $value + * + * @return mixed + * @throws \CRM_Core_Exception + */ + public function getRenewableMembershipValue(string $value) { + if (!$this->isDefined('RenewableMembership')) { + return NULL; + } + return $this->lookup('RenewableMembership', $value); + } + } diff --git a/www/modules/civicrm/CRM/Contribute/Form/Contribution/ThankYou.php b/www/modules/civicrm/CRM/Contribute/Form/Contribution/ThankYou.php index 2a146f154..f499158a9 100644 --- a/www/modules/civicrm/CRM/Contribute/Form/Contribution/ThankYou.php +++ b/www/modules/civicrm/CRM/Contribute/Form/Contribution/ThankYou.php @@ -44,11 +44,11 @@ public function preProcess(): void { $this->_params = $this->get('params'); $this->_useForMember = $this->get('useForMember'); - $this->assign('thankyou_title', CRM_Utils_Array::value('thankyou_title', $this->_values)); - $this->assign('thankyou_text', CRM_Utils_Array::value('thankyou_text', $this->_values)); - $this->assign('thankyou_footer', CRM_Utils_Array::value('thankyou_footer', $this->_values)); - $this->assign('max_reminders', CRM_Utils_Array::value('max_reminders', $this->_values)); - $this->assign('initial_reminder_day', CRM_Utils_Array::value('initial_reminder_day', $this->_values)); + $this->assign('thankyou_title', $this->_values['thankyou_title'] ?? NULL); + $this->assign('thankyou_text', $this->_values['thankyou_text'] ?? NULL); + $this->assign('thankyou_footer', $this->_values['thankyou_footer'] ?? NULL); + $this->assign('max_reminders', $this->_values['max_reminders'] ?? NULL); + $this->assign('initial_reminder_day', $this->_values['initial_reminder_day'] ?? NULL); $this->assignTotalAmounts(); // Link (button) for users to create their own Personal Campaign page if ($linkText = CRM_PCP_BAO_PCP::getPcpBlockStatus($this->getContributionPageID(), 'contribute')) { @@ -98,7 +98,7 @@ public function buildQuickForm() { $this->_ccid = $this->get('ccid'); $option = $this->get('option'); $membershipTypeID = $this->get('membershipTypeID'); - $this->assign('receiptFromEmail', CRM_Utils_Array::value('receipt_from_email', $this->_values)); + $this->assign('receiptFromEmail', $this->_values['receipt_from_email'] ?? NULL); if ($this->getProductID()) { $this->buildPremiumsBlock(FALSE, $option); diff --git a/www/modules/civicrm/CRM/Contribute/Form/ContributionBase.php b/www/modules/civicrm/CRM/Contribute/Form/ContributionBase.php index fbdda96ec..526140bb2 100644 --- a/www/modules/civicrm/CRM/Contribute/Form/ContributionBase.php +++ b/www/modules/civicrm/CRM/Contribute/Form/ContributionBase.php @@ -354,7 +354,7 @@ public function preProcess() { // current contribution page id $this->getContributionPageID(); $this->_ccid = CRM_Utils_Request::retrieve('ccid', 'Positive', $this); - $this->_emailExists = $this->get('emailExists'); + $this->_emailExists = $this->get('emailExists') ?? FALSE; $this->_contactID = $this->_membershipContactID = $this->getContactID(); @@ -764,7 +764,7 @@ public function assignEmailField() { } } else { - $this->assign('email', CRM_Utils_Array::value("email-{$this->_bltID}", $this->_params)); + $this->assign('email', $this->_params["email-{$this->_bltID}"] ?? NULL); } } @@ -925,7 +925,6 @@ protected function buildPremiumsBlock(bool $formItems = FALSE, $selectedOption = $this->add('hidden', 'selectProduct', $selectedProductID, ['id' => 'selectProduct']); $premiumProducts = PremiumsProduct::get() ->addSelect('product_id.*') - ->addSelect('product_id') ->addSelect('premiums_id.*') ->addWhere('product_id.is_active', '=', TRUE) ->addWhere('premiums_id.premiums_active', '=', TRUE) @@ -936,23 +935,14 @@ protected function buildPremiumsBlock(bool $formItems = FALSE, $selectedOption = $products = []; $premium = []; foreach ($premiumProducts as $premiumProduct) { - $product = ['options' => NULL]; - foreach ($premiumProduct as $key => $value) { - if (str_starts_with($key, 'product_id.')) { - if ($key === 'product_id.options' && $selectedProductID === $product['id'] && $selectedOption) { - // In this case we are on the thank you or confirm page so assign - // the selected option to the page for display. - $product['options'] = ts('Selected Option') . ': ' . $selectedOption; - } - else { - $product[str_replace('product_id.', '', $key)] = $value; - } - } - if (str_starts_with($key, 'premiums_id.')) { - $premium[str_replace('premiums_id.', '', $key)] = $value; - } + $product = CRM_Utils_Array::filterByPrefix($premiumProduct, 'product_id.'); + $premium = CRM_Utils_Array::filterByPrefix($premiumProduct, 'premiums_id.'); + if ($selectedProductID === $product['id'] && $selectedOption) { + // In this case we are on the thank you or confirm page so assign + // the selected option to the page for display. + $product['options'] = [ts('Selected Option') . ': ' . $selectedOption]; } - $options = array_filter(explode(',', $product['options'])); + $options = array_filter((array) $product['options']); $productOptions = []; foreach ($options as $option) { $optionValue = trim($option); @@ -963,10 +953,10 @@ protected function buildPremiumsBlock(bool $formItems = FALSE, $selectedOption = if (!empty($options)) { $this->addElement('select', 'options_' . $product['id'], NULL, $productOptions); } - $products[$premiumProduct['product_id']] = $product; + $products[$product['id']] = $product; } $this->assign('premiumBlock', $premium); - $this->assign('products', $products ?? NULL); + $this->assign('products', $products); } /** @@ -1117,7 +1107,7 @@ private function authenticatePledgeUser(): void { //check for valid pledge status. if (!in_array($pledgeValues['status_id'], $validStatus)) { - 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)])); + CRM_Core_Error::statusBounce(ts('Oops. You cannot make a payment for this pledge - pledge status is %1.', [1 => $allStatus[$pledgeValues['status_id']] ?? ''])); } } diff --git a/www/modules/civicrm/CRM/Contribute/Form/ContributionPage.php b/www/modules/civicrm/CRM/Contribute/Form/ContributionPage.php index ed25b5a6f..735719f45 100644 --- a/www/modules/civicrm/CRM/Contribute/Form/ContributionPage.php +++ b/www/modules/civicrm/CRM/Contribute/Form/ContributionPage.php @@ -347,7 +347,7 @@ public function endPostProcess() { } CRM_Core_Session::setStatus(ts("'%1' information has been saved.", - [1 => CRM_Utils_Array::value('title', CRM_Utils_Array::value($subPage, $this->get('tabHeader')), $className)] + [1 => $this->get('tabHeader')[$subPage]['title'] ?? $className] ), $this->getTitle(), 'success'); $this->postProcessHook(); diff --git a/www/modules/civicrm/CRM/Contribute/Form/ContributionPage/TabHeader.php b/www/modules/civicrm/CRM/Contribute/Form/ContributionPage/TabHeader.php index 1344cbb44..f567494b8 100644 --- a/www/modules/civicrm/CRM/Contribute/Form/ContributionPage/TabHeader.php +++ b/www/modules/civicrm/CRM/Contribute/Form/ContributionPage/TabHeader.php @@ -31,7 +31,7 @@ public static function build(&$form) { $tabs = self::process($form); $form->set('tabHeader', $tabs); } - $form->assign_by_ref('tabHeader', $tabs); + $form->assign('tabHeader', $tabs); CRM_Core_Resources::singleton() ->addScriptFile('civicrm', 'templates/CRM/common/TabHeader.js', 1, 'html-header') ->addSetting([ diff --git a/www/modules/civicrm/CRM/Contribute/Form/ContributionPage/Widget.php b/www/modules/civicrm/CRM/Contribute/Form/ContributionPage/Widget.php index 2f60fd96d..fb510995c 100644 --- a/www/modules/civicrm/CRM/Contribute/Form/ContributionPage/Widget.php +++ b/www/modules/civicrm/CRM/Contribute/Form/ContributionPage/Widget.php @@ -210,8 +210,8 @@ public function buildQuickForm() { ); } - $this->assign_by_ref('fields', $this->_fields); - $this->assign_by_ref('colorFields', $this->_colorFields); + $this->assign('fields', $this->_fields); + $this->assign('colorFields', $this->_colorFields); $this->_refreshButtonName = $this->getButtonName('refresh'); $this->addElement('xbutton', diff --git a/www/modules/civicrm/CRM/Contribute/Form/ContributionRecur.php b/www/modules/civicrm/CRM/Contribute/Form/ContributionRecur.php index d02af5e59..8adf529a5 100644 --- a/www/modules/civicrm/CRM/Contribute/Form/ContributionRecur.php +++ b/www/modules/civicrm/CRM/Contribute/Form/ContributionRecur.php @@ -14,6 +14,7 @@ * @package CRM * @copyright CiviCRM LLC https://civicrm.org/licensing */ +use Civi\Api4\Membership; /** * Shared parent class for recurring contribution forms. @@ -29,6 +30,8 @@ class CRM_Contribute_Form_ContributionRecur extends CRM_Core_Form { * Contribution ID. * * @var int + * + * @internal */ protected $_coid = NULL; @@ -37,7 +40,7 @@ class CRM_Contribute_Form_ContributionRecur extends CRM_Core_Form { * * @var int * - * @internal + * @deprecated */ protected $_crid = NULL; @@ -51,16 +54,16 @@ class CRM_Contribute_Form_ContributionRecur extends CRM_Core_Form { * * @internal */ - protected $contributionRecurID = NULL; + protected int $contributionRecurID; /** * Membership ID. * - * @var int + * @var int|null * * @internal */ - protected $_mid; + protected ?int $_mid; /** * Payment processor object. @@ -92,14 +95,6 @@ class CRM_Contribute_Form_ContributionRecur extends CRM_Core_Form { */ protected $selfService; - /** - * Used by `CRM_Contribute_Form_UpdateSubscription` - * - * @var CRM_Core_DAO - * @deprecated This is being set temporarily - we should eventually just use the getter fn. - */ - protected $_subscriptionDetails = NULL; - /** * Explicitly declare the entity api name. */ @@ -162,7 +157,7 @@ protected function setPaymentProcessor() { * Set the subscription details on the form. */ protected function setSubscriptionDetails() { - $this->subscriptionDetails = $this->_subscriptionDetails = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($this->getContributionRecurID()); + $this->subscriptionDetails = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($this->getContributionRecurID()); } /** @@ -194,15 +189,17 @@ protected function getSubscriptionContactID() { * @return int */ public function getContributionRecurID(): int { - $id = CRM_Utils_Request::retrieve('crid', 'Integer', $this, FALSE); - if (!$id && $this->getContributionID()) { - $id = $this->getContributionValue('contribution_recur_id'); - } - if (!$id) { - $id = $this->getMembershipValue('contribution_recur_id'); + if (!isset($this->contributionRecurID)) { + $id = CRM_Utils_Request::retrieve('crid', 'Integer', $this, FALSE); + if (!$id && $this->getContributionID()) { + $id = $this->getContributionValue('contribution_recur_id'); + } + if (!$id) { + $id = $this->getMembershipValue('contribution_recur_id'); + } + $this->contributionRecurID = $this->_crid = $id; } - $this->contributionRecurID = $this->_crid = $id; - return (int) $id; + return (int) $this->contributionRecurID; } /** @@ -222,15 +219,41 @@ public function getContributionID(): ?int { /** * Get the membership ID. * + * @return int + * @throws \CRM_Core_Exception + * * @api This function will not change in a minor release and is supported for * use outside of core. This annotation / external support for properties * is only given where there is specific test cover. * - * @return int */ protected function getMembershipID(): ?int { - $this->_mid = CRM_Utils_Request::retrieve('mid', 'Integer', $this, FALSE); - return $this->_mid ? (int) $this->_mid : NULL; + if (!CRM_Core_Component::isEnabled('CiviMember')) { + return NULL; + } + $membershipID = CRM_Utils_Request::retrieve('mid', 'Integer', $this); + if (!isset($this->contributionRecurID)) { + // This is being called before the contribution recur ID is set - return quickly to avoid a loop. + return $membershipID ? (int) $membershipID : NULL; + } + if (!isset($this->_mid)) { + $this->_mid = NULL; + if (!$this->isDefined('Membership')) { + $membership = Membership::get(FALSE) + ->addWhere('contribution_recur_id', '=', $this->getContributionRecurID()) + ->addSelect('*', 'membership_type_id.name') + ->execute()->first(); + if ($membershipID && (!$membership || ($membership['id'] !== $membershipID))) { + // this feels unreachable + throw new CRM_Core_Exception(ts('invalid membership ID')); + } + if ($membership) { + $this->define('Membership', 'Membership', $membership); + $this->_mid = $membership['id']; + } + } + } + return $this->_mid; } /** diff --git a/www/modules/civicrm/CRM/Contribute/Form/ContributionView.php b/www/modules/civicrm/CRM/Contribute/Form/ContributionView.php index 489bea9eb..4754de3b7 100644 --- a/www/modules/civicrm/CRM/Contribute/Form/ContributionView.php +++ b/www/modules/civicrm/CRM/Contribute/Form/ContributionView.php @@ -160,8 +160,16 @@ public function preProcess() { $productDAO->id = $productID; $productDAO->find(TRUE); + // If the option has a key/val that are not identical, display as "label (key)" + // where the "key" is somewhat assumed to be the SKU of the option + $options = CRM_Contribute_BAO_Premium::parseProductOptions($productDAO->options); + $option_key = $option_label = $dao->product_option; + if ($option_key && !empty($options[$option_key]) && $options[$option_key] != $option_key) { + $option_label = $options[$option_key] . ' (' . $option_key . ')'; + } + $this->assign('premium', $productDAO->name); - $this->assign('option', $dao->product_option); + $this->assign('option', $option_label); $this->assign('fulfilled', $dao->fulfilled_date); } diff --git a/www/modules/civicrm/CRM/Contribute/Form/Task/Invoice.php b/www/modules/civicrm/CRM/Contribute/Form/Task/Invoice.php index a11365a26..18b3c58e3 100644 --- a/www/modules/civicrm/CRM/Contribute/Form/Task/Invoice.php +++ b/www/modules/civicrm/CRM/Contribute/Form/Task/Invoice.php @@ -134,12 +134,12 @@ public function preProcess() { /** * Build the form object. */ - public function buildQuickForm() { + public function buildQuickForm(): void { $this->preventAjaxSubmit(); $this->assign('isAdmin', CRM_Core_Permission::check('administer CiviCRM')); $this->add('select', 'from_email_address', ts('From'), $this->_fromEmails, TRUE, ['class' => 'crm-select2 huge']); - if ($this->_selectedOutput != 'email') { + if ($this->_selectedOutput !== 'email') { $this->addElement('radio', 'output', NULL, ts('Email Invoice'), 'email_invoice'); $this->addElement('radio', 'output', NULL, ts('PDF Invoice'), 'pdf_invoice'); $this->addRule('output', ts('Selection required'), 'required'); @@ -162,7 +162,7 @@ public function buildQuickForm() { $this->addButtons([ [ 'type' => 'upload', - 'name' => $this->_selectedOutput == 'email' ? ts('Send Email') : ts('Process Invoice(s)'), + 'name' => $this->_selectedOutput === 'email' ? ts('Send Email') : ts('Process Invoice(s)'), 'isDefault' => TRUE, ], [ @@ -342,13 +342,13 @@ public static function printPDF($contribIDs, &$params, $contactIds) { ]; CRM_Core_DAO::commonRetrieveAll($daoName, 'id', $pageId, $mailDetails, $mailElements); - $values['title'] = CRM_Utils_Array::value('title', CRM_Utils_Array::value($contribution->contribution_page_id, $mailDetails)); - $values['receipt_from_name'] = CRM_Utils_Array::value('receipt_from_name', CRM_Utils_Array::value($contribution->contribution_page_id, $mailDetails)); - $values['receipt_from_email'] = CRM_Utils_Array::value('receipt_from_email', CRM_Utils_Array::value($contribution->contribution_page_id, $mailDetails)); - $values['cc_receipt'] = CRM_Utils_Array::value('cc_receipt', CRM_Utils_Array::value($contribution->contribution_page_id, $mailDetails)); - $values['bcc_receipt'] = CRM_Utils_Array::value('bcc_receipt', CRM_Utils_Array::value($contribution->contribution_page_id, $mailDetails)); + $values['title'] = $mailDetails[$contribution->contribution_page_id]['title'] ?? NULL; + $values['receipt_from_name'] = $mailDetails[$contribution->contribution_page_id]['receipt_from_name'] ?? NULL; + $values['receipt_from_email'] = $mailDetails[$contribution->contribution_page_id]['receipt_from_email'] ?? NULL; + $values['cc_receipt'] = $mailDetails[$contribution->contribution_page_id]['cc_receipt'] ?? NULL; + $values['bcc_receipt'] = $mailDetails[$contribution->contribution_page_id]['bcc_receipt'] ?? NULL; - $title = CRM_Utils_Array::value('title', CRM_Utils_Array::value($contribution->contribution_page_id, $mailDetails)); + $title = $mailDetails[$contribution->contribution_page_id]['title'] ?? NULL; } // @todo - use token in template, stop assigning. $source = $contribution->source; @@ -376,7 +376,7 @@ public static function printPDF($contribIDs, &$params, $contactIds) { $countryDomain = ''; } - $invoiceNotes = Civi::settings()->get('invoice_notes') ?? NULL; + $invoiceNotes = Civi::settings()->get('invoice_notes'); // parameters to be assign for template $tplParams = [ @@ -443,16 +443,16 @@ public static function printPDF($contribIDs, &$params, $contactIds) { 'is_pay_later' => $contribution->is_pay_later, 'organization_name' => Contact::get(FALSE)->addSelect('organization_name')->addWhere('id', '=', (int) $contribution->contact_id)->execute()->first()['organization_name'], 'domain_organization' => $domain->name, - 'domain_street_address' => CRM_Utils_Array::value('street_address', CRM_Utils_Array::value('1', $locationDefaults['address'])), - 'domain_supplemental_address_1' => CRM_Utils_Array::value('supplemental_address_1', CRM_Utils_Array::value('1', $locationDefaults['address'])), - 'domain_supplemental_address_2' => CRM_Utils_Array::value('supplemental_address_2', CRM_Utils_Array::value('1', $locationDefaults['address'])), - 'domain_supplemental_address_3' => CRM_Utils_Array::value('supplemental_address_3', CRM_Utils_Array::value('1', $locationDefaults['address'])), - 'domain_city' => CRM_Utils_Array::value('city', CRM_Utils_Array::value('1', $locationDefaults['address'])), - 'domain_postal_code' => CRM_Utils_Array::value('postal_code', CRM_Utils_Array::value('1', $locationDefaults['address'])), + 'domain_street_address' => $locationDefaults['address']['1']['street_address'] ?? NULL, + 'domain_supplemental_address_1' => $locationDefaults['address']['1']['supplemental_address_1'] ?? NULL, + 'domain_supplemental_address_2' => $locationDefaults['address']['1']['supplemental_address_2'] ?? NULL, + 'domain_supplemental_address_3' => $locationDefaults['address']['1']['supplemental_address_3'] ?? NULL, + 'domain_city' => $locationDefaults['address']['1']['city'] ?? NULL, + 'domain_postal_code' => $locationDefaults['address']['1']['postal_code'] ?? NULL, 'domain_state' => $stateProvinceAbbreviationDomain, 'domain_country' => $countryDomain, - 'domain_email' => CRM_Utils_Array::value('email', CRM_Utils_Array::value('1', $locationDefaults['email'])), - 'domain_phone' => CRM_Utils_Array::value('phone', CRM_Utils_Array::value('1', $locationDefaults['phone'])), + 'domain_email' => $locationDefaults['email']['1']['email'] ?? NULL, + 'domain_phone' => $locationDefaults['phone']['1']['phone'] ?? NULL, ]; if (isset($creditNoteId)) { diff --git a/www/modules/civicrm/CRM/Contribute/Form/Task/PDF.php b/www/modules/civicrm/CRM/Contribute/Form/Task/PDF.php index b48c98da1..04b263ccd 100644 --- a/www/modules/civicrm/CRM/Contribute/Form/Task/PDF.php +++ b/www/modules/civicrm/CRM/Contribute/Form/Task/PDF.php @@ -61,7 +61,8 @@ public function preProcess() { 'title' => ts('Search Results'), ], ]; - CRM_Contact_Form_Task_EmailCommon ::preProcessFromAddress($this, FALSE); + $this->_contactIds = $this->_contactIds ?: [CRM_Core_Session::getLoggedInContactID()]; + $this->preProcessFromAddress(); // we have all the contribution ids, so now we get the contact ids parent::setContactIDs(); CRM_Utils_System::appendBreadCrumb($breadCrumb); @@ -70,6 +71,29 @@ public function preProcess() { $this->preventAjaxSubmit(); } + /** + * Pre Process Form Addresses to be used in Quickform + * + * @throws \CRM_Core_Exception + */ + private function preProcessFromAddress() { + $fromEmailValues = CRM_Core_BAO_Email::getFromEmail(); + + if (empty($fromEmailValues)) { + CRM_Core_Error::statusBounce(ts('Your user record does not have a valid email address and no from addresses have been configured.')); + } + + $defaults = []; + if (is_numeric(key($fromEmailValues))) { + $emailID = (int) key($fromEmailValues); + $defaults = CRM_Core_BAO_Email::getEmailSignatureDefaults($emailID); + } + if (!Civi::settings()->get('allow_mail_from_logged_in_contact')) { + $defaults['from_email_address'] = CRM_Core_BAO_Domain::getFromEmail(); + } + $this->setDefaults($defaults); + } + /** * Build the form object. */ diff --git a/www/modules/civicrm/CRM/Contribute/Form/Task/Status.php b/www/modules/civicrm/CRM/Contribute/Form/Task/Status.php index 6a71ef6bf..d45a37a43 100644 --- a/www/modules/civicrm/CRM/Contribute/Form/Task/Status.php +++ b/www/modules/civicrm/CRM/Contribute/Form/Task/Status.php @@ -127,7 +127,7 @@ public function buildQuickForm() { $this->_rows[] = $row; } - $this->assign_by_ref('rows', $this->_rows); + $this->assign('rows', $this->_rows); $this->setDefaults($defaults); $this->addButtons([ [ diff --git a/www/modules/civicrm/CRM/Contribute/Form/UpdateBilling.php b/www/modules/civicrm/CRM/Contribute/Form/UpdateBilling.php index 176fda771..849df1c4d 100644 --- a/www/modules/civicrm/CRM/Contribute/Form/UpdateBilling.php +++ b/www/modules/civicrm/CRM/Contribute/Form/UpdateBilling.php @@ -40,20 +40,20 @@ public function preProcess() { $this->_paymentProcessor['object'] = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->_coid, 'contribute', 'obj'); } - if ($this->_mid) { - $this->_paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->_mid, 'membership', 'info'); - $this->_paymentProcessor['object'] = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->_mid, 'membership', 'obj'); + if ($this->getMembershipID()) { + $this->_paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->getMembershipID(), 'membership', 'info'); + $this->_paymentProcessor['object'] = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->getMembershipID(), 'membership', 'obj'); $membershipTypes = CRM_Member_PseudoConstant::membershipType(); - $membershipTypeId = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership', $this->_mid, 'membership_type_id'); - $this->assign('membershipType', CRM_Utils_Array::value($membershipTypeId, $membershipTypes)); + $membershipTypeId = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership', $this->getMembershipID(), 'membership_type_id'); + $this->assign('membershipType', $membershipTypes[$membershipTypeId] ?? NULL); $this->_mode = 'auto_renew'; } - if ((!$this->_crid && !$this->_coid && !$this->_mid) || (!$this->getSubscriptionDetails())) { + if ((!$this->_crid && !$this->_coid && !$this->getMembershipID()) || (!$this->getSubscriptionDetails())) { throw new CRM_Core_Exception('Required information missing.'); } - if (!$this->_paymentProcessor['object']->supports('updateSubscriptionBillingInfo')) { + if (!$this->getPaymentProcessorObject()->supports('updateSubscriptionBillingInfo')) { throw new CRM_Core_Exception(ts("%1 processor doesn't support updating subscription billing details.", [1 => $this->_paymentProcessor['title']] )); @@ -201,7 +201,7 @@ public function postProcess() { $processorParams['amount'] = $this->getSubscriptionDetails()->amount; $processorParams['contributionRecurID'] = $this->getContributionRecurID(); $message = ''; - $updateSubscription = $this->_paymentProcessor['object']->updateSubscriptionBillingInfo($message, $processorParams); + $updateSubscription = $this->getPaymentProcessorObject()->updateSubscriptionBillingInfo($message, $processorParams); if (is_a($updateSubscription, 'CRM_Core_Error')) { CRM_Core_Error::displaySessionError($updateSubscription); } diff --git a/www/modules/civicrm/CRM/Contribute/Form/UpdateSubscription.php b/www/modules/civicrm/CRM/Contribute/Form/UpdateSubscription.php index e68c61818..488d4a392 100644 --- a/www/modules/civicrm/CRM/Contribute/Form/UpdateSubscription.php +++ b/www/modules/civicrm/CRM/Contribute/Form/UpdateSubscription.php @@ -24,11 +24,10 @@ * made here could potentially affect the API etc. Be careful, be aware, use unit tests. */ class CRM_Contribute_Form_UpdateSubscription extends CRM_Contribute_Form_ContributionRecur { + use CRM_Custom_Form_CustomDataTrait; public $_paymentProcessor = NULL; - public $_paymentProcessorObj = NULL; - /** * Fields that affect the schedule and are defined as editable by the processor. * @@ -60,41 +59,21 @@ public function preProcess() { parent::preProcess(); $this->setAction(CRM_Core_Action::UPDATE); - if ($this->_coid) { - $this->_paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->_coid, 'contribute', 'info'); - // @todo test & replace with $this->_paymentProcessorObj = Civi\Payment\System::singleton()->getById($this->_paymentProcessor['id']); - $this->_paymentProcessorObj = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->_coid, 'contribute', 'obj'); - $this->contributionRecurID = $this->_subscriptionDetails->recur_id; - } - elseif ($this->contributionRecurID) { - $this->_coid = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $this->contributionRecurID, 'id', 'contribution_recur_id'); - } - - if (!$this->contributionRecurID || !$this->_subscriptionDetails) { + if (!$this->getSubscriptionDetails()) { CRM_Core_Error::statusBounce(ts('Required information missing.')); } - if ($this->_subscriptionDetails->membership_id && $this->_subscriptionDetails->auto_renew) { - // Add Membership details to form - $membership = civicrm_api3('Membership', 'get', [ - 'contribution_recur_id' => $this->contributionRecurID, - ]); - if (!empty($membership['count'])) { - $membershipDetails = reset($membership['values']); - $values['membership_id'] = $membershipDetails['id']; - $values['membership_name'] = $membershipDetails['membership_name']; - } - $this->assign('recurMembership', $values); - $this->assign('contactId', $this->_subscriptionDetails->contact_id); - } + $this->assign('contactId', $this->getSubscriptionContactID()); + $this->assign('membershipID', $this->getMembershipID()); + $this->assign('membershipName', $this->getMembershipValue('membership_type_id.name')); $this->assign('self_service', $this->isSelfService()); - $this->assign('recur_frequency_interval', $this->_subscriptionDetails->frequency_interval); - $this->assign('recur_frequency_unit', $this->_subscriptionDetails->frequency_unit); + $this->assign('recur_frequency_interval', $this->getContributionRecurValue('frequency_interval')); + $this->assign('recur_frequency_unit', $this->getContributionRecurValue('frequency_unit')); - $this->editableScheduleFields = $this->_paymentProcessorObj->getEditableRecurringScheduleFields(); + $this->editableScheduleFields = $this->getPaymentProcessorObject()->getEditableRecurringScheduleFields(); - $changeHelpText = $this->_paymentProcessorObj->getRecurringScheduleUpdateHelpText(); + $changeHelpText = $this->getPaymentProcessorObject()->getRecurringScheduleUpdateHelpText(); if (!in_array('amount', $this->editableScheduleFields)) { // Not sure if this is good behaviour - maintaining this existing behaviour for now. CRM_Core_Session::setStatus($changeHelpText, ts('Warning'), 'alert'); @@ -109,17 +88,20 @@ public function preProcess() { } } - // when custom data is included in this page - if (!empty($_POST['hidden_custom']) && !$this->isSelfService()) { - CRM_Custom_Form_CustomData::preProcess($this, NULL, NULL, 1, 'ContributionRecur', $this->contributionRecurID); - CRM_Custom_Form_CustomData::buildQuickForm($this); - CRM_Custom_Form_CustomData::setDefaultValues($this); + if ($this->isSubmitted() && !$this->isSelfService()) { + // The custom data fields are added to the form by an ajax form. + // However, if they are not present in the element index they will + // not be available from `$this->getSubmittedValue()` in post process. + // We do not have to set defaults or otherwise render - just add to the element index. + $this->addCustomDataFieldsToForm('ContributionRecur', array_filter([ + 'id' => $this->getContributionRecurID(), + ])); } $this->assign('editableScheduleFields', array_diff($this->editableScheduleFields, $alreadyHardCodedFields)); - if ($this->_subscriptionDetails->contact_id) { - $contactDetails = CRM_Contact_BAO_Contact::getContactDetails($this->_subscriptionDetails->contact_id); + if ($this->getSubscriptionContactID()) { + $contactDetails = CRM_Contact_BAO_Contact::getContactDetails($this->getSubscriptionContactID()); $this->_donorEmail = $contactDetails[1]; } @@ -136,12 +118,12 @@ public function preProcess() { */ public function setDefaultValues() { $this->_defaults = []; - $this->_defaults['amount'] = $this->_subscriptionDetails->amount; - $this->_defaults['installments'] = $this->_subscriptionDetails->installments; - $this->_defaults['campaign_id'] = $this->_subscriptionDetails->campaign_id; - $this->_defaults['financial_type_id'] = $this->_subscriptionDetails->financial_type_id; + $this->_defaults['amount'] = $this->getSubscriptionDetails()->amount; + $this->_defaults['installments'] = $this->getSubscriptionDetails()->installments; + $this->_defaults['campaign_id'] = $this->getSubscriptionDetails()->campaign_id; + $this->_defaults['financial_type_id'] = $this->getSubscriptionDetails()->financial_type_id; foreach ($this->editableScheduleFields as $field) { - $this->_defaults[$field] = $this->_subscriptionDetails->$field ?? NULL; + $this->_defaults[$field] = $this->getSubscriptionDetails()->$field ?? NULL; } return $this->_defaults; @@ -153,12 +135,12 @@ public function setDefaultValues() { public function buildQuickForm() { // CRM-16398: If current recurring contribution got > 1 lineitems then make amount field readonly $amtAttr = ['size' => 20]; - $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($this->_coid); + $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($this->getContributionID()); if (count($lineItems) > 1) { $amtAttr += ['readonly' => TRUE]; } $amountField = $this->addMoney('amount', ts('Recurring Contribution Amount'), TRUE, $amtAttr, - TRUE, 'currency', $this->_subscriptionDetails->currency, TRUE + TRUE, 'currency', $this->getSubscriptionDetails()->currency, TRUE ); // https://lab.civicrm.org/dev/financial/-/issues/197 https://github.com/civicrm/civicrm-core/pull/23796 @@ -180,16 +162,14 @@ public function buildQuickForm() { } if (CRM_Core_Permission::check('edit contributions')) { - CRM_Campaign_BAO_Campaign::addCampaign($this, $this->_subscriptionDetails->campaign_id); + CRM_Campaign_BAO_Campaign::addCampaign($this, $this->getSubscriptionDetails()->campaign_id); } - if (CRM_Contribute_BAO_ContributionRecur::supportsFinancialTypeChange($this->contributionRecurID)) { + if (CRM_Contribute_BAO_ContributionRecur::supportsFinancialTypeChange($this->getContributionRecurID())) { $this->addEntityRef('financial_type_id', ts('Financial Type'), ['entity' => 'FinancialType'], !$this->isSelfService()); } - // Add custom data - $this->assign('customDataType', 'ContributionRecur'); - $this->assign('entityID', $this->contributionRecurID); + $this->assign('contributionRecurID', $this->getContributionRecurID()); $type = 'next'; if ($this->isSelfService()) { @@ -226,14 +206,14 @@ public function postProcess() { // if this is an update of an existing recurring contribution, pass the ID $params['contributionRecurID'] = $params['id'] = $this->getContributionRecurID(); - $message = ''; + $activityDetails = ''; $params['recurProcessorID'] = $params['subscriptionId'] = $this->getSubscriptionDetails()->processor_id; $updateSubscription = TRUE; - if ($this->_paymentProcessorObj->supports('changeSubscriptionAmount')) { + if ($this->getPaymentProcessorObject()->supports('changeSubscriptionAmount')) { try { - $updateSubscription = $this->_paymentProcessorObj->changeSubscriptionAmount($message, $params); + $updateSubscription = $this->getPaymentProcessorObject()->changeSubscriptionAmount($activityDetails, $params); if ($updateSubscription instanceof CRM_Core_Error) { CRM_Core_Error::deprecatedWarning('An exception should be thrown'); throw new PaymentProcessorException(ts('Could not update the Recurring contribution details')); @@ -245,49 +225,30 @@ public function postProcess() { } if ($updateSubscription) { // Handle custom data - $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params, $this->contributionRecurID, 'ContributionRecur'); + $params['custom'] = CRM_Core_BAO_CustomField::postProcess($this->getSubmittedValues(), $this->getContributionRecurID(), 'ContributionRecur'); // save the changes CRM_Contribute_BAO_ContributionRecur::add($params); $status = ts('Recurring contribution has been updated to: %1, every %2 %3(s) for %4 installments.', [ - 1 => CRM_Utils_Money::format($params['amount'], $this->_subscriptionDetails->currency), - 2 => $this->_subscriptionDetails->frequency_interval, - 3 => $this->_subscriptionDetails->frequency_unit, + 1 => CRM_Utils_Money::format($params['amount'], $this->getSubscriptionDetails()->currency), + 2 => $this->getSubscriptionDetails()->frequency_interval, + 3 => $this->getSubscriptionDetails()->frequency_unit, 4 => $params['installments'], ] ); $msgTitle = ts('Update Success'); $msgType = 'success'; - $msg = ts('Recurring Contribution Updated'); - $contactID = $this->_subscriptionDetails->contact_id; - - if ($this->_subscriptionDetails->amount != $params['amount']) { - $message .= "
" . ts("Recurring contribution amount has been updated from %1 to %2 for this subscription.", - [ - 1 => CRM_Utils_Money::format($this->_subscriptionDetails->amount, $this->_subscriptionDetails->currency), - 2 => CRM_Utils_Money::format($params['amount'], $this->_subscriptionDetails->currency), - ]) . ' '; - if ($this->_subscriptionDetails->amount < $params['amount']) { - $msg = ts('Recurring Contribution Updated - increased installment amount'); - } - else { - $msg = ts('Recurring Contribution Updated - decreased installment amount'); - } - } + $activitySubject = ts('Recurring Contribution Updated'); + $contactID = $this->getSubscriptionContactID(); - if ($this->_subscriptionDetails->installments != $params['installments']) { - $message .= "
" . ts("Recurring contribution installments have been updated from %1 to %2 for this subscription.", [ - 1 => $this->_subscriptionDetails->installments, - 2 => $params['installments'], - ]) . ' '; - } + $this->updateActivitySubjectAndDetails($params, $activitySubject, $activityDetails); $activityParams = [ 'source_contact_id' => $contactID, 'activity_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Update Recurring Contribution'), - 'subject' => $msg, - 'details' => $message, + 'subject' => $activitySubject, + 'details' => $activityDetails, 'activity_date_time' => date('YmdHis'), 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed'), ]; @@ -311,7 +272,7 @@ public function postProcess() { 'workflow' => 'contribution_recurring_edit', 'contactId' => $contactID, 'tplParams' => ['receipt_from_email' => $receiptFrom], - 'isTest' => $this->_subscriptionDetails->is_test, + 'isTest' => $this->getSubscriptionDetails()->is_test, 'PDFFilename' => 'receipt.pdf', 'from' => $receiptFrom, 'toName' => $donorDisplayName, @@ -337,4 +298,44 @@ public function postProcess() { } } + private function updateActivitySubjectAndDetails(array $params, string &$activitySubject, string &$activityDetails): void { + if ($this->getSubscriptionDetails()->amount != $params['amount']) { + $activityDetails .= "
" . ts("Recurring contribution amount has been updated from %1 to %2 for this subscription.", + [ + 1 => CRM_Utils_Money::format($this->getSubscriptionDetails()->amount, $this->getSubscriptionDetails()->currency), + 2 => CRM_Utils_Money::format($params['amount'], $this->getSubscriptionDetails()->currency), + ]) . ' '; + if ($this->getSubscriptionDetails()->amount < $params['amount']) { + $activitySubject = ts('Recurring Contribution Updated - increased installment amount'); + } + else { + $activitySubject = ts('Recurring Contribution Updated - decreased installment amount'); + } + } + + if ($this->getSubscriptionDetails()->installments != $params['installments']) { + $activityDetails .= "
" . ts("Recurring contribution installments have been updated from %1 to %2 for this subscription.", [ + 1 => $this->getSubscriptionDetails()->installments, + 2 => $params['installments'], + ]) . ' '; + } + + if (!empty($params['cycle_day']) && $this->getSubscriptionDetails()->cycle_day != $params['cycle_day']) { + $activityDetails .= "
" . ts("Cycle day has been updated from %1 to %2 for this subscription.", [ + 1 => $this->getSubscriptionDetails()->cycle_day, + 2 => $params['cycle_day'], + ]) . ' '; + } + + if ( + !empty($params['next_sched_contribution_date']) && + $this->getSubscriptionDetails()->next_sched_contribution_date != $params['next_sched_contribution_date'] + ) { + $activityDetails .= "
" . ts("Next scheduled contribution date has been updated from %1 to %2 for this subscription.", [ + 1 => $this->getSubscriptionDetails()->next_sched_contribution_date, + 2 => $params['next_sched_contribution_date'], + ]) . ' '; + } + } + } diff --git a/www/modules/civicrm/CRM/Contribute/Import/Controller.php b/www/modules/civicrm/CRM/Contribute/Import/Controller.php deleted file mode 100644 index 8ec9f32d1..000000000 --- a/www/modules/civicrm/CRM/Contribute/Import/Controller.php +++ /dev/null @@ -1,41 +0,0 @@ -_stateMachine = new CRM_Import_StateMachine($this, $action); - - // create and instantiate the pages - $this->addPages($this->_stateMachine, $action); - - // add all the actions - $config = CRM_Core_Config::singleton(); - $this->addActions($config->uploadDir, ['uploadFile']); - } - -} diff --git a/www/modules/civicrm/CRM/Contribute/Import/Parser/Contribution.php b/www/modules/civicrm/CRM/Contribute/Import/Parser/Contribution.php index f5bb954fc..cba6f73f7 100644 --- a/www/modules/civicrm/CRM/Contribute/Import/Parser/Contribution.php +++ b/www/modules/civicrm/CRM/Contribute/Import/Parser/Contribution.php @@ -26,13 +26,6 @@ */ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { - /** - * Array of successfully imported contribution id's - * - * @var array - */ - protected $_newContributions; - protected $baseEntity = 'Contribution'; /** @@ -63,18 +56,6 @@ public static function getUserJobInfo(): array { */ const SOFT_CREDIT = 512, SOFT_CREDIT_ERROR = 1024, PLEDGE_PAYMENT = 2048, PLEDGE_PAYMENT_ERROR = 4096; - /** - * Separator being used - * @var string - */ - protected $_separator; - - /** - * Array of pledge payment error lines, bounded by MAX_ERROR - * @var array - */ - protected $_pledgePaymentErrors; - /** * Get the field mappings for the import. * @@ -452,7 +433,7 @@ public function import(array $values): void { } } - $this->deprecatedFormatParams($contributionParams, $contributionParams); + $this->deprecatedFormatParams($contributionParams); // From this point on we are changing stuff - the prior rows were doing lookups and exiting // if the lookups failed. @@ -583,14 +564,10 @@ private function processPledgePayments(int $contributionID, array $formatted): b * convert it into the same format that we use in QF and BAO object * * @param array $params - * Associative array of property name/value - * pairs to insert in new contact. - * @param array $values - * The reformatted properties that we can use internally. * * @throws \CRM_Core_Exception */ - private function deprecatedFormatParams($params, &$values): void { + private function deprecatedFormatParams(&$params): void { // copy all the contribution fields as is if (empty($params['pledge_id'])) { return; @@ -622,14 +599,13 @@ private function deprecatedFormatParams($params, &$values): void { if (CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_Pledge', $params['pledge_id'], 'contact_id') != $contributionContactID) { throw new CRM_Core_Exception('Invalid Pledge ID provided. Contribution row was skipped.', CRM_Import_Parser::ERROR); } - $values['pledge_id'] = $params['pledge_id']; } // we need to check if oldest payment amount equal to contribution amount - $pledgePaymentDetails = CRM_Pledge_BAO_PledgePayment::getOldestPledgePayment($values['pledge_id']); + $pledgePaymentDetails = CRM_Pledge_BAO_PledgePayment::getOldestPledgePayment($params['pledge_id']); if ($pledgePaymentDetails['amount'] == $totalAmount) { - $values['pledge_payment_id'] = $pledgePaymentDetails['id']; + $params['pledge_payment_id'] = $pledgePaymentDetails['id']; } else { throw new CRM_Core_Exception('Contribution and Pledge Payment amount mismatch for this record. Contribution row was skipped.', CRM_Import_Parser::ERROR); diff --git a/www/modules/civicrm/CRM/Contribute/Info.php b/www/modules/civicrm/CRM/Contribute/Info.php index 43a70a08c..8002daa32 100644 --- a/www/modules/civicrm/CRM/Contribute/Info.php +++ b/www/modules/civicrm/CRM/Contribute/Info.php @@ -51,45 +51,25 @@ public function getInfo() { /** * @inheritDoc - * Provides permissions that are used by component. - * Needs to be implemented in component's information - * class. - * - * NOTE: if using conditionally permission return, - * implementation of $getAllUnconditionally is required. - * - * @param bool $getAllUnconditionally - * @param bool $descriptions - * Whether to return permission descriptions - * - * @return array|null - * collection of permissions, null if none */ - public function getPermissions($getAllUnconditionally = FALSE, $descriptions = FALSE) { + public function getPermissions(): array { $permissions = [ 'access CiviContribute' => [ - ts('access CiviContribute'), - ts('Record backend contributions (with edit contributions) and view all contributions (for visible contacts)'), + 'label' => ts('access CiviContribute'), + 'description' => ts('Record backend contributions (with edit contributions) and view all contributions (for visible contacts)'), ], 'edit contributions' => [ - ts('edit contributions'), - ts('Record and update contributions'), + 'label' => ts('edit contributions'), + 'description' => ts('Record and update contributions'), ], 'make online contributions' => [ - ts('make online contributions'), + 'label' => ts('make online contributions'), ], 'delete in CiviContribute' => [ - ts('delete in CiviContribute'), - ts('Delete contributions'), + 'label' => ts('delete in CiviContribute'), + 'description' => ts('Delete contributions'), ], ]; - - if (!$descriptions) { - foreach ($permissions as $name => $attr) { - $permissions[$name] = array_shift($attr); - } - } - return $permissions; } diff --git a/www/modules/civicrm/CRM/Contribute/Page/ContributionPage.php b/www/modules/civicrm/CRM/Contribute/Page/ContributionPage.php index 89de37866..f90b1259c 100644 --- a/www/modules/civicrm/CRM/Contribute/Page/ContributionPage.php +++ b/www/modules/civicrm/CRM/Contribute/Page/ContributionPage.php @@ -674,7 +674,7 @@ public function pager($whereClause, $whereParams) { $params['total'] = CRM_Core_DAO::singleValueQuery($query, $whereParams); $this->_pager = new CRM_Utils_Pager($params); - $this->assign_by_ref('pager', $this->_pager); + $this->assign('pager', $this->_pager); } /** diff --git a/www/modules/civicrm/CRM/Contribute/Page/Tab.php b/www/modules/civicrm/CRM/Contribute/Page/Tab.php index b9ed69a9b..f2f845d72 100644 --- a/www/modules/civicrm/CRM/Contribute/Page/Tab.php +++ b/www/modules/civicrm/CRM/Contribute/Page/Tab.php @@ -269,15 +269,12 @@ public function browse() { private function addRecurringContributionsBlock() { [$activeContributions, $activeContributionsCount] = $this->getActiveRecurringContributions(); [$inactiveRecurringContributions, $inactiveContributionsCount] = $this->getInactiveRecurringContributions(); - - if (!empty($activeContributions) || !empty($inactiveRecurringContributions)) { - // assign vars to templates - $this->assign('action', $this->_action); - $this->assign('activeRecurRows', $activeContributions); - $this->assign('contributionRecurCount', $activeContributionsCount + $inactiveContributionsCount); - $this->assign('inactiveRecurRows', $inactiveRecurringContributions); - $this->assign('recur', TRUE); - } + // assign vars to templates + $this->assign('action', $this->_action); + $this->assign('activeRecurRows', $activeContributions); + $this->assign('contributionRecurCount', $activeContributionsCount + $inactiveContributionsCount); + $this->assign('inactiveRecurRows', $inactiveRecurringContributions); + $this->assign('recur', !empty($activeContributions) || !empty($inactiveRecurringContributions)); } /** diff --git a/www/modules/civicrm/CRM/Contribute/Page/UserDashboard.php b/www/modules/civicrm/CRM/Contribute/Page/UserDashboard.php index 5da9f38cc..0f8ff15fd 100644 --- a/www/modules/civicrm/CRM/Contribute/Page/UserDashboard.php +++ b/www/modules/civicrm/CRM/Contribute/Page/UserDashboard.php @@ -80,7 +80,7 @@ public function listContribution(): void { $action = array_sum(array_keys(CRM_Contribute_Page_Tab::dashboardRecurLinks((int) $recur['id'], (int) $recur['contact_id']))); - $details = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($recur['id'], 'recur'); + $details = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($recur['id']); $hideUpdate = $details->membership_id & $details->auto_renew; if ($hideUpdate) { diff --git a/www/modules/civicrm/CRM/Contribute/Selector/Search.php b/www/modules/civicrm/CRM/Contribute/Selector/Search.php index 180fbbbeb..8f2570a19 100644 --- a/www/modules/civicrm/CRM/Contribute/Selector/Search.php +++ b/www/modules/civicrm/CRM/Contribute/Selector/Search.php @@ -373,9 +373,7 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $row['campaign_id'] = $result->contribution_campaign_id; // add contribution status name - $row['contribution_status_name'] = CRM_Utils_Array::value($row['contribution_status_id'], - $contributionStatuses - ); + $row['contribution_status_name'] = $contributionStatuses[$row['contribution_status_id']] ?? NULL; $isPayLater = FALSE; if ($result->is_pay_later && ($row['contribution_status_name'] ?? NULL) === 'Pending') { @@ -440,7 +438,7 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { (int) $result->contribution_id ); - $row['contact_type'] = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ? $result->contact_sub_type : $result->contact_type, FALSE, $result->contact_id + $row['contact_type'] = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ?: $result->contact_type, FALSE, $result->contact_id ); if (!empty($row['amount_level'])) { diff --git a/www/modules/civicrm/CRM/Contribute/WorkflowMessage/RecurringEdit/AlexCancelled.php b/www/modules/civicrm/CRM/Contribute/WorkflowMessage/RecurringEdit/AlexCancelled.php index 846f99b1b..247674879 100644 --- a/www/modules/civicrm/CRM/Contribute/WorkflowMessage/RecurringEdit/AlexCancelled.php +++ b/www/modules/civicrm/CRM/Contribute/WorkflowMessage/RecurringEdit/AlexCancelled.php @@ -20,7 +20,7 @@ public function build(array &$example): void { $example['asserts'] = [ 'default' => [ ['for' => 'subject', 'regex' => '/Recurring Contribution Update.*Alex/'], - ['for' => 'text', 'regex' => '/Recurring contribution is for €5,990.99, every 2 year.s. for 24 installments/'], + ['for' => 'html', 'regex' => '/Recurring contribution is for €5,990.99, every 2 year.s. for 24 installments/'], ], ]; } diff --git a/www/modules/civicrm/CRM/Core/Action.php b/www/modules/civicrm/CRM/Core/Action.php index 7188b40ed..3bf7891f8 100644 --- a/www/modules/civicrm/CRM/Core/Action.php +++ b/www/modules/civicrm/CRM/Core/Action.php @@ -309,7 +309,11 @@ public static function formLink( } if ($op && $objectName && $objectId) { + $oldLinks = $seqLinks; CRM_Utils_Hook::links($op, $objectName, $objectId, $seqLinks, $mask, $values); + if ($oldLinks !== $seqLinks) { + Civi::log()->warning('Tabular screens that call hook_civicrm_links are being replaced by SearchKit. See https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_links/#notes', ['civi.tag' => 'deprecated']); + } } $url = []; diff --git a/www/modules/civicrm/CRM/Core/BAO/ActionSchedule.php b/www/modules/civicrm/CRM/Core/BAO/ActionSchedule.php index d6e876fc1..25234d618 100644 --- a/www/modules/civicrm/CRM/Core/BAO/ActionSchedule.php +++ b/www/modules/civicrm/CRM/Core/BAO/ActionSchedule.php @@ -543,7 +543,7 @@ protected static function createMailingActivity($tokenRow, $mapping, $contactID, $activityParams = [ 'subject' => $tokenRow->render('subject'), 'details' => $tokenRow->render('body_html'), - 'source_contact_id' => $session->get('userID') ? $session->get('userID') : $contactID, + 'source_contact_id' => $session->get('userID') ?: $contactID, 'target_contact_id' => $contactID, // @todo - not required with api 'activity_date_time' => CRM_Utils_Time::getTime('YmdHis'), @@ -615,7 +615,7 @@ protected static function sendReminderSms($tokenRow, $schedule, $toContactID) { $sms_body_text = $tokenRow->render('sms_body_text'); $session = CRM_Core_Session::singleton(); - $userID = $session->get('userID') ? $session->get('userID') : $tokenRow->context['contactId']; + $userID = $session->get('userID') ?: $tokenRow->context['contactId']; $smsParams = [ 'To' => $toPhoneNumber, 'provider_id' => $schedule->sms_provider_id, diff --git a/www/modules/civicrm/CRM/Core/BAO/Address.php b/www/modules/civicrm/CRM/Core/BAO/Address.php index e9d10dbb1..70d4ce8a6 100644 --- a/www/modules/civicrm/CRM/Core/BAO/Address.php +++ b/www/modules/civicrm/CRM/Core/BAO/Address.php @@ -294,6 +294,11 @@ public static function dataExists(&$params) { $config = CRM_Core_Config::singleton(); foreach ($params as $name => $value) { + if (is_array($value) && str_starts_with($name, 'custom_')) { + // This could be a custom field of type file. We want to unset these as they could + // give false positives. + unset($value['error'], $value['size']); + } if (in_array($name, [ 'is_primary', 'location_type_id', diff --git a/www/modules/civicrm/CRM/Core/BAO/Block.php b/www/modules/civicrm/CRM/Core/BAO/Block.php index fe5df456d..736910df5 100644 --- a/www/modules/civicrm/CRM/Core/BAO/Block.php +++ b/www/modules/civicrm/CRM/Core/BAO/Block.php @@ -236,7 +236,7 @@ public static function create($blockName, $params) { $valueId = FALSE; if ($blockName == 'phone') { $phoneTypeBlockValue = $blockValue['phoneTypeId'] ?? NULL; - if ($phoneTypeBlockValue == CRM_Utils_Array::value('phone_type_id', $value)) { + if ($phoneTypeBlockValue == ($value['phone_type_id'] ?? NULL)) { $valueId = TRUE; } } @@ -385,7 +385,7 @@ public static function handlePrimary(&$params, $class) { * is_primary to 1 * @see https://issues.civicrm.org/jira/browse/CRM-10451 */ - if ($existingEntities->N == 1 && $existingEntities->id == CRM_Utils_Array::value('id', $params)) { + if ($existingEntities->N == 1 && $existingEntities->id == ($params['id'] ?? NULL)) { $params['is_primary'] = 1; return; } diff --git a/www/modules/civicrm/CRM/Core/BAO/Cache.php b/www/modules/civicrm/CRM/Core/BAO/Cache.php index 77c530f61..b71cb95c0 100644 --- a/www/modules/civicrm/CRM/Core/BAO/Cache.php +++ b/www/modules/civicrm/CRM/Core/BAO/Cache.php @@ -59,6 +59,9 @@ public static function resetCaches() { * Should session state be reset on completion of DB store?. */ public static function storeSessionToCache($names, $resetSession = TRUE) { + \Civi::dispatcher()->dispatch('civi.session.storeObjects', \Civi\Core\Event\GenericHookEvent::create([ + 'names' => &$names, + ])); foreach ($names as $key => $sessionName) { if (is_array($sessionName)) { $value = NULL; @@ -102,6 +105,9 @@ public static function storeSessionToCache($names, $resetSession = TRUE) { * @param array $names */ public static function restoreSessionFromCache($names) { + \Civi::dispatcher()->dispatch('civi.session.restoreObjects', \Civi\Core\Event\GenericHookEvent::create([ + 'names' => &$names, + ])); foreach ($names as $key => $sessionName) { if (is_array($sessionName)) { $value = Civi::cache('session')->get("{$sessionName[0]}_{$sessionName[1]}"); diff --git a/www/modules/civicrm/CRM/Core/BAO/Country.php b/www/modules/civicrm/CRM/Core/BAO/Country.php index 8d53a8bf8..02b21ac1d 100644 --- a/www/modules/civicrm/CRM/Core/BAO/Country.php +++ b/www/modules/civicrm/CRM/Core/BAO/Country.php @@ -180,7 +180,7 @@ public static function defaultCurrencySymbol($defaultCurrency = NULL) { 'labelColumn' => 'symbol', 'orderColumn' => TRUE, ]); - $cachedSymbol = CRM_Utils_Array::value($currency, $currencySymbols, ''); + $cachedSymbol = $currencySymbols[$currency] ?? ''; } else { $cachedSymbol = '$'; diff --git a/www/modules/civicrm/CRM/Core/BAO/CustomField.php b/www/modules/civicrm/CRM/Core/BAO/CustomField.php index 6e81545bf..7f021beb1 100644 --- a/www/modules/civicrm/CRM/Core/BAO/CustomField.php +++ b/www/modules/civicrm/CRM/Core/BAO/CustomField.php @@ -15,7 +15,6 @@ * @copyright CiviCRM LLC https://civicrm.org/licensing */ -use Civi\Api4\CustomField; use Civi\Api4\Utils\CoreUtil; /** @@ -23,13 +22,6 @@ */ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField { - /** - * Array to hold (formatted) fields for import - * - * @var array - */ - public static $_importFields = NULL; - /** * Build and retrieve the list of data types and descriptions. * @@ -240,7 +232,7 @@ public static function writeRecords(array $records): array { $op = empty($records[$index]['id']) ? 'create' : 'edit'; // Theoretically a custom field could have custom fields! Trippy... if (!empty($records[$index]['custom']) && is_array($records[$index]['custom'])) { - CRM_Core_BAO_CustomValueTable::store($records[$index]['custom'], static::$_tableName, $customField->id, $op); + CRM_Core_BAO_CustomValueTable::store($records[$index]['custom'], static::getTableName(), $customField->id, $op); } CRM_Utils_Hook::post($op, 'CustomField', $customField->id, $customField); } @@ -322,10 +314,13 @@ public static function buildOptions($fieldName, $context = NULL, $props = []) { } /** - * Store and return an array of all active custom fields. + * Crufty function makes getting custom fields unnecessarily difficult. + * @deprecated since 5.71 + * @see CRM_Core_BAO_CustomGroup::getAll + * for a better alternative. * * @param string $customDataType - * Type of Custom Data; 'ANY' is a synonym for "all contact data types". + * Type of Custom Data; 'ANY' is a synonym for "all entity types". * @param bool $showAll * If true returns all fields (includes disabled fields). * @param bool $inline @@ -360,7 +355,7 @@ public static function &getFields( $checkPermission = CRM_Core_Permission::EDIT; } if (empty($customDataType)) { - $customDataType = array_merge(['Contact'], CRM_Contact_BAO_ContactType::basicTypes()); + $customDataType = CRM_Contact_BAO_ContactType::basicTypes(); } if ($customDataType === 'ANY') { // NULL should have been respected but the line above broke that. @@ -368,7 +363,6 @@ public static function &getFields( $customDataType = NULL; } if ($customDataType && !is_array($customDataType)) { - if (in_array($customDataType, CRM_Contact_BAO_ContactType::subTypes(), TRUE)) { // This is the case when getFieldsForImport() requires fields // limited strictly to a subtype. @@ -376,290 +370,90 @@ public static function &getFields( $customDataType = CRM_Contact_BAO_ContactType::getBasicType($customDataType); $onlySubType = TRUE; } - - if (array_key_exists($customDataType, CRM_Core_SelectValues::customGroupExtends())) { - // this makes the method flexible to support retrieving fields - // for multiple extends value. - $customDataType = [$customDataType]; - } + $customDataType = (array) $customDataType; } - $customDataSubType = CRM_Utils_Array::explodePadded($customDataSubType); + $filters = []; - if (is_array($customDataType)) { - $cacheKey = implode('_', $customDataType); + if ($customDataType) { + // Contact type should also include "Contact" + if (array_intersect($customDataType, CRM_Contact_BAO_ContactType::basicTypes(TRUE))) { + $customDataType[] = 'Contact'; + } + $filters['extends'] = $customDataType; } - else { - $cacheKey = $customDataType; + if (!$showAll) { + $filters['is_active'] = TRUE; } - - $cacheKey .= !empty($customDataSubType) ? ('_' . implode('_', $customDataSubType)) : '_0'; - $cacheKey .= $customDataSubName ? "{$customDataSubName}_" : '_0'; - $cacheKey .= $showAll ? '_1' : '_0'; - $cacheKey .= $inline ? '_1_' : '_0_'; - $cacheKey .= $onlyParent ? '_1_' : '_0_'; - $cacheKey .= $onlySubType ? '_1_' : '_0_'; - $cacheKey .= $checkPermission ? $checkPermission . CRM_Core_Session::getLoggedInContactID() . '_' : '_0_0_'; - $cacheKey .= '_' . CRM_Core_Config::domainID() . '_'; - - $cgTable = CRM_Core_DAO_CustomGroup::getTableName(); - - // also get the permission stuff here - if ($checkPermission) { - $permissionClause = CRM_Core_Permission::customGroupClause($checkPermission, - "{$cgTable}." - ); + if ($onlyParent) { + $filters['extends_entity_column_value'] = NULL; + $filters['extends_entity_column_id'] = NULL; } - else { - $permissionClause = '(1)'; + if ($customDataSubName) { + $filters['extends_entity_column_id'] = $customDataSubName; } - - // lets md5 permission clause and take first 8 characters - $cacheKey .= substr(md5($permissionClause), 0, 8); - - if (strlen($cacheKey) > 40) { - $cacheKey = md5($cacheKey); + if ($inline) { + $filters['style'] = 'Inline'; } - - if (!isset(self::$_importFields[$cacheKey])) { - if (!self::$_importFields) { - self::$_importFields = []; - } - - // check if we can retrieve from database cache - $fields = Civi::Cache('fields')->get("custom importableFields $cacheKey"); - - if ($fields === NULL) { - - $extends = ''; - if (is_array($customDataType)) { - $value = NULL; - foreach ($customDataType as $dataType) { - if (array_key_exists($dataType, CRM_Core_SelectValues::customGroupExtends())) { - if (in_array($dataType, CRM_Contact_BAO_ContactType::basicTypes(TRUE), TRUE)) { - $val = "'" . CRM_Utils_Type::escape($dataType, 'String') . "', 'Contact' "; - } - else { - $val = "'" . CRM_Utils_Type::escape($dataType, 'String') . "'"; - } - $value = $value ? $value . ", {$val}" : $val; - } - } - if ($value) { - $extends = "AND $cgTable.extends IN ( $value ) "; - } - } - - if (!empty($customDataType) && empty($extends)) { - // $customDataType specified a filter, but there is no corresponding SQL ($extends) - self::$_importFields[$cacheKey] = []; - return self::$_importFields[$cacheKey]; - } - - if ($onlyParent) { - $extends .= " AND $cgTable.extends_entity_column_value IS NULL AND $cgTable.extends_entity_column_id IS NULL "; - } - // Temporary hack - in 5.27 a new field is added to civicrm_custom_field. There is a high - // risk this function is called before the upgrade page can be reached and if - // so it will potentially result in fatal error. - $serializeField = CRM_Core_BAO_Domain::isDBVersionAtLeast('5.27.alpha1') ? "custom_field.serialize," : ''; - - $query = "SELECT custom_field.id, custom_field.label, - $cgTable.title, - custom_field.data_type, - custom_field.html_type, - custom_field.default_value, - custom_field.options_per_line, custom_field.text_length, - custom_field.custom_group_id, - custom_field.is_required, - custom_field.column_name, - $cgTable.extends, custom_field.is_search_range, - $cgTable.extends_entity_column_value, - $cgTable.extends_entity_column_id, - custom_field.is_view, - custom_field.option_group_id, - custom_field.date_format, - custom_field.time_format, - $cgTable.is_multiple, - $serializeField - $cgTable.table_name, - og.name as option_group_name - FROM civicrm_custom_field custom_field - INNER JOIN $cgTable - ON custom_field.custom_group_id = $cgTable.id - LEFT JOIN civicrm_option_group og - ON custom_field.option_group_id = og.id - WHERE ( 1 ) "; - - if (!$showAll) { - $query .= " AND custom_field.is_active = 1 AND $cgTable.is_active = 1 "; - } - - if ($inline) { - $query .= " AND $cgTable.style = 'Inline' "; - } - - //get the custom fields for specific type in - //combination with fields those support any type. - if (!empty($customDataSubType)) { - $subtypeClause = []; - foreach ($customDataSubType as $subtype) { - $subtype = CRM_Core_DAO::VALUE_SEPARATOR . CRM_Utils_Type::escape($subtype, 'String') . CRM_Core_DAO::VALUE_SEPARATOR; - $subtypeClause[] = "$cgTable.extends_entity_column_value LIKE '%{$subtype}%'"; - } - if (!$onlySubType) { - $subtypeClause[] = "$cgTable.extends_entity_column_value IS NULL"; - } - $query .= " AND ( " . implode(' OR ', $subtypeClause) . " )"; - } - - if ($customDataSubName) { - $query .= " AND ( $cgTable.extends_entity_column_id = $customDataSubName ) "; - } - - // also get the permission stuff here - if ($checkPermission) { - $permissionClause = CRM_Core_Permission::customGroupClause($checkPermission, - "{$cgTable}.", TRUE - ); - } - else { - $permissionClause = '(1)'; - } - - $query .= " $extends AND $permissionClause - ORDER BY $cgTable.weight, $cgTable.title, - custom_field.weight, custom_field.label"; - - $dao = CRM_Core_DAO::executeQuery($query); - - $fields = []; - while (($dao->fetch()) != NULL) { - $regexp = preg_replace('/[.,;:!?]/', '', ''); - $fields[$dao->id]['id'] = $dao->id; - $fields[$dao->id]['label'] = $dao->label; - // This seems broken, but not in a new way. - $fields[$dao->id]['headerPattern'] = '/' . preg_quote($regexp, '/') . '/'; - // To support the consolidation of various functions & their expectations. - $fields[$dao->id]['title'] = $dao->label; - $fields[$dao->id]['custom_field_id'] = $dao->id; - $fields[$dao->id]['groupTitle'] = $dao->title; - $fields[$dao->id]['data_type'] = $dao->data_type; - $fields[$dao->id]['name'] = 'custom_' . $dao->id; - $fields[$dao->id]['type'] = self::dataToType()[$dao->data_type] ?? NULL; - $fields[$dao->id]['html_type'] = $dao->html_type; - $fields[$dao->id]['default_value'] = $dao->default_value; - $fields[$dao->id]['text_length'] = $dao->text_length; - $fields[$dao->id]['options_per_line'] = $dao->options_per_line; - $fields[$dao->id]['custom_group_id'] = $dao->custom_group_id; - $fields[$dao->id]['extends'] = $dao->extends; - $fields[$dao->id]['is_search_range'] = $dao->is_search_range; - $fields[$dao->id]['extends_entity_column_value'] = $dao->extends_entity_column_value; - $fields[$dao->id]['extends_entity_column_id'] = $dao->extends_entity_column_id; - $fields[$dao->id]['is_view'] = $dao->is_view; - $fields[$dao->id]['is_multiple'] = $dao->is_multiple; - $fields[$dao->id]['option_group_id'] = $dao->option_group_id; - $fields[$dao->id]['date_format'] = $dao->date_format; - $fields[$dao->id]['time_format'] = $dao->time_format; - $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'] = $serializeField ? $dao->serialize : (int) self::isSerialized($dao); - $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] : ''; - if (in_array($dao->extends, CRM_Contact_BAO_ContactType::subTypes())) { - // if $extends is a subtype, refer contact table - $fields[$dao->id]['extends_table'] = 'civicrm_contact'; - } - // Search table is used by query object searches.. - $fields[$dao->id]['search_table'] = ($fields[$dao->id]['extends_table'] == 'civicrm_contact') ? 'contact_a' : $fields[$dao->id]['extends_table']; - self::getOptionsForField($fields[$dao->id], $dao->option_group_name); - } - - Civi::cache('fields')->set("custom importableFields $cacheKey", $fields); + if (!empty($customDataSubType)) { + $filters['extends_entity_column_value'] = CRM_Utils_Array::explodePadded($customDataSubType); + if (!$onlySubType) { + $filters['extends_entity_column_value'][] = NULL; } - self::$_importFields[$cacheKey] = $fields; } - - return self::$_importFields[$cacheKey]; - } - - /** - * Get all active custom fields (cached wrapper). - * - * @param false|int $permissionType - * - Either FALSE (do not check) or CRM_Core_Permission::VIEW or CRM_Core_Permission::EDIT - * - * @return array - * List of customField details keyed by customFieldID - * @throws \CRM_Core_Exception - */ - public static function getAllCustomFields($permissionType): array { - if ($permissionType !== FALSE && !is_int($permissionType)) { - throw new CRM_Core_Exception('permissionCheck must be FALSE or CRM_Core_Permission::VIEW or CRM_Core_Permission::EDIT'); - } - $cacheString = __CLASS__ . __FUNCTION__ . CRM_Core_Config::domainID() . '_' . CRM_Core_I18n::getLocale(); - if ($permissionType) { - $cacheString .= 'check_' . $permissionType . '_user_' . CRM_Core_Session::getLoggedInContactID(); - } - if (!Civi::cache('metadata')->has($cacheString)) { - $apiCall = CustomField::get(FALSE) - ->addOrderBy('custom_group_id.title') - ->addOrderBy('custom_group_id.weight') - ->addOrderBy('weight') - ->addOrderBy('label') - ->addSelect('*') - ->addSelect('custom_group_id.extends') - ->addSelect('custom_group_id.extends_entity_column_id') - ->addSelect('custom_group_id.extends_entity_column_value') - ->addSelect('custom_group_id.is_active') - ->addSelect('custom_group_id.name') - ->addSelect('custom_group_id.title') - ->addSelect('custom_group_id.table_name') - ->addSelect('custom_group_id.is_public'); - if ($permissionType && !CRM_Core_Permission::customGroupAdmin()) { - $availableGroups = CRM_Core_Permission::customGroup($permissionType); - $apiCall->addWhere('custom_group_id', 'IN', empty($availableGroups) ? [0] : $availableGroups); - } - - $types = (array) $apiCall->execute()->indexBy('id'); - - Civi::cache('metadata')->set($cacheString, $types); - } - return Civi::cache('metadata')->get($cacheString); - } - - /** - * Get all active custom fields for the given contact type. - * - * This is formatted as an apiv4 Style array. - * - * @param string $contactType - * @param bool|int $permissionType - * - Either FALSE (do not check) or CRM_Core_Permission::VIEW or CRM_Core_Permission::EDIT - * @param array $contactSubTypes - * - * @return array $fields - * - * @throws \CRM_Core_Exception - */ - public static function getCustomFieldsForContactType(string $contactType, $permissionType, array $contactSubTypes = []): array { + $customGroups = CRM_Core_BAO_CustomGroup::getAll($filters, $checkPermission ?: NULL); $fields = []; - foreach (self::getAllCustomFields($permissionType) as $field) { - if ($field['custom_group_id.extends'] === $contactType || $field['custom_group_id.extends'] === 'Contact') { - if (empty($contactSubTypes) || empty($field['custom_group_id.extends_entity_column_value'])) { - $fields[$field['id']] = $field; - } - else { - foreach ($contactSubTypes as $contactSubType) { - if (in_array($contactSubType, $field['custom_group_id.extends_entity_column_value'], TRUE)) { - $fields[$field['id']] = $field; - } - } - } + + // This function has been refactored to use `CRM_Core_BAO_CustomGroup::getAll` instead + // of a database query. They old query results were badly formatted, but since this function is + // deprecated, the following loop recreates the bad formatting instead of fixing it, for + // maximum backward-compatibility. + // (for forward-compatibility... don't use this function) + foreach ($customGroups as $customGroup) { + foreach ($customGroup['fields'] as $customField) { + $id = (string) $customField['id']; + $fields[$id]['id'] = $id; + $fields[$id]['label'] = $customField['label']; + // This seems broken, but not in a new way. + $fields[$id]['headerPattern'] = '//'; + // To support the consolidation of various functions & their expectations. + $fields[$id]['title'] = $customField['label']; + $fields[$id]['custom_field_id'] = $id; + $fields[$id]['groupTitle'] = $customGroup['title']; + $fields[$id]['data_type'] = $customField['data_type']; + $fields[$id]['name'] = 'custom_' . $id; + $fields[$id]['type'] = self::dataToType()[$customField['data_type']] ?? NULL; + $fields[$id]['html_type'] = $customField['html_type']; + $fields[$id]['default_value'] = $customField['default_value']; + $fields[$id]['text_length'] = $customField['text_length']; + $fields[$id]['options_per_line'] = $customField['options_per_line']; + $fields[$id]['custom_group_id'] = (string) $customGroup['id']; + $fields[$id]['extends'] = $customGroup['extends']; + $fields[$id]['is_search_range'] = (string) (int) $customField['is_search_range']; + $fields[$id]['extends_entity_column_value'] = CRM_Utils_Array::implodePadded($customGroup['extends_entity_column_value']); + $fields[$id]['extends_entity_column_id'] = (string) $customGroup['extends_entity_column_id']; + $fields[$id]['is_view'] = (string) (int) $customField['is_view']; + $fields[$id]['is_multiple'] = (string) (int) $customGroup['is_multiple']; + $fields[$id]['option_group_id'] = ((string) $customField['option_group_id']) ?: NULL; + $fields[$id]['date_format'] = $customField['date_format']; + $fields[$id]['time_format'] = $customField['time_format']; + $fields[$id]['is_required'] = (string) (int) $customField['is_required']; + $fields[$id]['table_name'] = $customGroup['table_name']; + $fields[$id]['column_name'] = $customField['column_name']; + $fields[$id]['serialize'] = $customField['serialize']; + $fields[$id]['where'] = $customGroup['table_name'] . '.' . $customField['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[$id]['extends_table'] = array_key_exists($customGroup['extends'], CRM_Core_BAO_CustomQuery::$extendsMap) ? CRM_Core_BAO_CustomQuery::$extendsMap[$customGroup['extends']] : ''; + if (in_array($customGroup['extends'], CRM_Contact_BAO_ContactType::subTypes())) { + // if $extends is a subtype, refer contact table + $fields[$id]['extends_table'] = 'civicrm_contact'; + } + // Search table is used by query object searches.. + $fields[$id]['search_table'] = ($fields[$id]['extends_table'] == 'civicrm_contact') ? 'contact_a' : $fields[$id]['extends_table']; + self::getOptionsForField($fields[$id]); } } + return $fields; } @@ -697,7 +491,7 @@ public static function getFieldsForImport( } // Note: there are situations when we want getFieldsForImport() return fields related // ONLY to basic contact types, but NOT subtypes. And thats where $onlyParent is helpful - $fields = &self::getFields($contactType, + $fields = self::getFields($contactType, $showAll, FALSE, NULL, @@ -756,119 +550,41 @@ public static function getKeyID($key, $all = FALSE) { } /** - * Use the cache to get all values of a specific custom field. + * Get specific custom field as an object. + * @deprecated + * @see CRM_Core_BAO_CustomField::getField() + * which does the same thing but returns an array instead of an object. * * @param int $fieldID * The custom field ID. * * @return CRM_Core_BAO_CustomField - * The field object. * @throws CRM_Core_Exception */ public static function getFieldObject($fieldID) { $field = new CRM_Core_BAO_CustomField(); - - // check if we can get the field values from the system cache - $cacheKey = "CRM_Core_DAO_CustomField_{$fieldID}"; - $cache = CRM_Utils_Cache::singleton(); - $fieldValues = $cache->get($cacheKey); - if (empty($fieldValues)) { - $field->id = $fieldID; - if (!$field->find(TRUE)) { - throw new CRM_Core_Exception('Cannot find Custom Field ' . $fieldID); - } - $fieldValues = []; - CRM_Core_DAO::storeValues($field, $fieldValues); - - $cache->set($cacheKey, $fieldValues); - } - else { - $field->copyValues($fieldValues); - } - + $field->copyValues(self::getField($fieldID)); return $field; } /** - * Use the cache to get all values of a specific custom field. + * Get all values of a specific custom field + its custom group. * * @param int $id * The custom field ID. - * @param int|false $permissionType * - * @return array + * @return array|null * The field object. * @throws CRM_Core_Exception */ - public static function getField(int $id, $permissionType = FALSE): array { - $field = self::getAllCustomFields($permissionType)[$id]; - // @todo - on the fence about caching these in the cache for all custom fields. The down side is the - // cache array could get really big & serializing & un-serializing big arrays is expensive. - $entity = in_array($field['custom_group_id.extends'], CRM_Contact_BAO_ContactType::basicTypes(TRUE), TRUE) ? 'Contact' : $field['custom_group_id.extends']; - $field['options'] = self::getFieldOptions($field['id'], $field['option_group_id'], $field['data_type'], $entity); - return $field; - } - - /** - * Gets an array of custom fields that are public. - * - * @internal do not use from untested or external code - signature may change. - * - * This takes into account - * - the is_public setting on the Custom Group - * - the is_view permission on the field (these are generally suppressed). - * - * @param string $extends - * @param array $extendsEntity - * Keyed by name values from CRM_Core_BAO_CustomGroup::getExtendsEntityColumnIdOptions() - * Values can be an int or an array. - * eg ['ParticipantRole' = [1], 'ParticipantEventType' => 2], ['ParticipantEventName' => 3] - * @param int $permissionType - * - * @return array - * @throws \CRM_Core_Exception - */ - public static function getViewableCustomFields(string $extends, array $extendsEntity = [], $permissionType = CRM_Core_Permission::VIEW): array { - $entityFilters = []; - // Convert from ['ParticipantRole' = [1], 'ParticipantEventType' => 2], ['ParticipantEventName' => 3] - // to [1 => [1], 2 => [2], 3 = [3] - if (!empty($extendsEntity)) { - $entityColumns = CRM_Core_BAO_CustomGroup::getExtendsEntityColumnIdOptions(); - foreach ($entityColumns as $entityColumn) { - if (isset($extendsEntity[$entityColumn['name']])) { - $entityFilters[(int) $entityColumn['id']] = (array) $extendsEntity[$entityColumn['name']]; - foreach ($entityFilters[$entityColumn['id']] as &$value) { - // Cast to string because we don't want the calling function to have to worry - // but also the array intersect fails otherwise. - $value = (string) $value; - } - } + public static function getField(int $id): ?array { + foreach (CRM_Core_BAO_CustomGroup::getAll() as $customGroup) { + if (isset($customGroup['fields'][$id])) { + $customGroup['fields'][$id]['custom_group'] = array_diff_key($customGroup, ['fields' => 1]); + return $customGroup['fields'][$id]; } } - $cacheKey = $extends . $permissionType . CRM_Core_Config::domainID() . '_' . CRM_Core_I18n::getLocale() . substr(md5(json_encode($extendsEntity)), 0, 30); - if (!isset(\Civi::$statics[__CLASS__][__FUNCTION__][$cacheKey])) { - \Civi::$statics[__CLASS__][__FUNCTION__][$cacheKey] = []; - $allFields = CRM_Core_BAO_CustomField::getAllCustomFields($permissionType); - foreach ($allFields as $field) { - $entityValueMatches = array_intersect((array) $field['custom_group_id.extends_entity_column_value'], ($entityFilters[$field['custom_group_id.extends_entity_column_id']] ?? [])); - if ( - !$field['is_view'] - && $field['custom_group_id.is_public'] - && ( - !empty($entityValueMatches) - || empty($entityFilters) - || empty($field['custom_group_id.extends_entity_column_id']) - ) - && ( - $field['custom_group_id.extends'] === $extends - || ($field['custom_group_id.extends'] === 'Contact' && in_array($extends, ['Individual', 'Organization', 'Household'])) - ) - ) { - \Civi::$statics[__CLASS__][__FUNCTION__][$cacheKey][(int) $field['id']] = $field; - } - } - } - return \Civi::$statics[__CLASS__][__FUNCTION__][$cacheKey]; + return NULL; } /** @@ -1251,6 +967,7 @@ public static function displayValue($value, $fieldID, $entityID = NULL) { $fieldID = is_object($fieldID) ? $fieldID->id : (int) str_replace('custom_', '', $fieldID); } $fieldInfo = self::getField($fieldID); + $fieldInfo['options'] = self::getFieldOptions($fieldID, $fieldInfo['option_group_id'], $fieldInfo['data_type'], $fieldInfo['custom_group']['extends']); $displayValue = self::formatDisplayValue($value, $fieldInfo, $entityID); // Call hook to alter display value. CRM_Utils_Hook::alterCustomFieldDisplayValue($displayValue, $value, $entityID, $fieldInfo); @@ -1466,9 +1183,7 @@ public static function setProfileDefaults( $value = NULL ) { //get the type of custom field - $customField = new CRM_Core_BAO_CustomField(); - $customField->id = $customFieldId; - $customField->find(TRUE); + $customField = self::getFieldObject($customFieldId); if (!$contactId) { if ($mode == CRM_Profile_Form::MODE_CREATE) { @@ -2596,7 +2311,8 @@ public static function postProcess( $customData = []; foreach ($params as $key => $value) { - if ($customFieldInfo = CRM_Core_BAO_CustomField::getKeyID($key, TRUE)) { + $customFieldInfo = CRM_Core_BAO_CustomField::getKeyID($key, TRUE); + if ($customFieldInfo[0]) { // for autocomplete transfer hidden value instead of label if ($params[$key] && isset($params[$key . '_id'])) { @@ -2623,7 +2339,8 @@ public static function postProcess( } /** - * Get custom field ID from field/group name/title. + * @deprecated Old function with weirdly ambiguous logic. + * Only used by APIv3. Use at your peril. * * @param string $fieldName Field name or label * @param string|null $groupName (Optional) Group name or label @@ -2666,7 +2383,7 @@ public static function getCustomFieldID($fieldName, $groupName = NULL, $fullStri } /** - * Given ID of a custom field, return its name as well as the name of the custom group it belongs to. + * @deprecated Old function only used by APIv3. * * @param array $ids * @@ -2724,24 +2441,14 @@ public static function validateCustomData($params) { //lets start w/ params. foreach ($params as $key => $value) { $customFieldID = self::getKeyID($key); - if (!$customFieldID) { + $field = $customFieldID ? CRM_Core_BAO_CustomField::getField($customFieldID) : NULL; + if (!$field) { continue; } - - //load the structural info for given field. - $field = new CRM_Core_DAO_CustomField(); - $field->id = $customFieldID; - if (!$field->find(TRUE)) { - continue; - } - $dataType = $field->data_type; - $profileField = $profileFields[$key] ?? []; $fieldTitle = $profileField['title'] ?? NULL; $isRequired = $profileField['is_required'] ?? NULL; - if (!$fieldTitle) { - $fieldTitle = $field->label; - } + $fieldTitle = CRM_Utils_String::purifyHTML($fieldTitle ?: $field['label']); //no need to validate. if (CRM_Utils_System::isNull($value) && !$isRequired) { @@ -2756,7 +2463,7 @@ public static function validateCustomData($params) { //now time to take care of custom field form rules. $ruleName = $errorMsg = NULL; - switch ($dataType) { + switch ($field['data_type']) { case 'Int': $ruleName = 'integer'; $errorMsg = ts('%1 must be an integer (whole number).', [1 => $fieldTitle]); @@ -2794,33 +2501,19 @@ public static function validateCustomData($params) { } /** - * Is this field a multi record field. + * If custom field belongs to a multi-record group, return the group id. * - * @param int $customId + * @param string|int $customId + * Either the numeric id or a string like "custom_xx" * - * @return bool + * @return int|false */ public static function isMultiRecordField($customId) { - $isMultipleWithGid = FALSE; if (!is_numeric($customId)) { $customId = self::getKeyID($customId); } - if (is_numeric($customId)) { - $sql = "SELECT cg.id cgId - FROM civicrm_custom_group cg - INNER JOIN civicrm_custom_field cf - ON cg.id = cf.custom_group_id -WHERE cf.id = %1 AND cg.is_multiple = 1"; - $params[1] = [$customId, 'Integer']; - $dao = CRM_Core_DAO::executeQuery($sql, $params); - if ($dao->fetch()) { - if ($dao->cgId) { - $isMultipleWithGid = $dao->cgId; - } - } - } - - return $isMultipleWithGid; + $customGroup = self::getField((int) $customId)['custom_group'] ?? NULL; + return empty($customGroup['is_multiple']) ? FALSE : $customGroup['id']; } /** @@ -2867,13 +2560,20 @@ public static function isSerialized($field) { return is_object($field) ? !empty($field->serialize) : !empty($field['serialize']); } + public static function getFkEntityOnDeleteOptions(): array { + return [ + 'set_null' => ts('Delete reference'), + 'cascade' => ts('Delete entity'), + ]; + } + /** * Get api3 entity name for this field * @deprecated * @return string */ public function getEntity() { - $entity = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $this->custom_group_id, 'extends'); + $entity = self::getField($this->id)['custom_group']['extends']; return in_array($entity, CRM_Contact_BAO_ContactType::basicTypes(TRUE), TRUE) ? 'Contact' : $entity; } @@ -2882,22 +2582,21 @@ public function getEntity() { * @return string */ public function getEntityName(): string { - $isMultiple = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $this->custom_group_id, 'is_multiple'); - if ($isMultiple) { - $groupName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $this->custom_group_id, 'name'); - return "Custom_$groupName"; + $customGroup = self::getField($this->id)['custom_group']; + if ($customGroup['is_multiple']) { + return "Custom_{$customGroup['name']}"; } - return CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $this->custom_group_id, 'extends'); + return $customGroup['extends']; } /** * Set pseudoconstant properties for field metadata. * * @param array $field - * @param string|null $optionGroupName */ - private static function getOptionsForField(&$field, $optionGroupName) { - if ($optionGroupName) { + private static function getOptionsForField(&$field) { + if (!empty($field['option_group_id'])) { + $optionGroupName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', $field['option_group_id']); $field['pseudoconstant'] = [ 'optionGroupName' => $optionGroupName, 'optionEditPath' => 'civicrm/admin/options/' . $optionGroupName, @@ -2999,6 +2698,7 @@ private static function getFieldOptions(int $id, ?int $optionGroupID, string $da $cache = CRM_Utils_Cache::singleton(); $options = $cache->get($cacheKey); if (!isset($options)) { + $entity = in_array($entity, CRM_Contact_BAO_ContactType::basicTypes(TRUE)) ? 'Contact' : $entity; // This will hold the list of options in format key => label $options = []; diff --git a/www/modules/civicrm/CRM/Core/BAO/CustomGroup.php b/www/modules/civicrm/CRM/Core/BAO/CustomGroup.php index a4bf0c909..5058863fa 100644 --- a/www/modules/civicrm/CRM/Core/BAO/CustomGroup.php +++ b/www/modules/civicrm/CRM/Core/BAO/CustomGroup.php @@ -28,6 +28,146 @@ public static function self_hook_civicrm_post(\Civi\Core\Event\PostEvent $e): vo Civi::cache('metadata')->flush(); } + /** + * Retrieve a group by id, name, etc. + * + * @param array $filter + * Simplified version of $filters in self::getAll; returns first group that matches every filter. + * e.g. `['id' => 23]` or `['name' => 'MyGroup']` + * @param int|null $permissionType + * Check permission: (CRM_Core_Permission::VIEW | CRM_Core_Permission::EDIT) + * @param int|null $userId + * User contact id for permission check (defaults to current user) + * @return array|null + * Result includes all custom fields in addition to group info. + */ + public static function getGroup(array $filter, int $permissionType = NULL, int $userId = NULL): ?array { + $allGroups = self::getAll([], $permissionType, $userId); + if (isset($filter['id']) && count($filter) === 1) { + return $allGroups[$filter['id']] ?? NULL; + } + foreach ($allGroups as $group) { + if (array_intersect_assoc($filter, $group) === $filter) { + return $group; + } + } + return NULL; + } + + /** + * Return custom groups and fields in a nested array, with optional filters and permissions applied. + * + * With no params, this returns every custom group and field, including disabled. + * + * @param array $filters + * [key => value] pairs to filter each custom group. + * - $filters[extends] will auto-expand Contact types (if passed as a string) + * - $filters[is_active] will also filter the fields + * @param int|null $permissionType + * Check permission: (CRM_Core_Permission::VIEW | CRM_Core_Permission::EDIT) + * @param int|null $userId + * User contact id for permission check (defaults to current user) + * @return array[] + */ + public static function getAll(array $filters = [], int $permissionType = NULL, int $userId = NULL): array { + $allGroups = self::loadAll(); + if (isset($permissionType)) { + if (!in_array($permissionType, [CRM_Core_Permission::EDIT, CRM_Core_Permission::VIEW], TRUE)) { + throw new CRM_Core_Exception('permissionType must be CRM_Core_Permission::VIEW or CRM_Core_Permission::EDIT'); + } + $permittedIds = CRM_Core_Permission::customGroup($permissionType, FALSE, $userId); + $allGroups = array_intersect_key($allGroups, array_flip($permittedIds)); + } + if (!$filters) { + return $allGroups; + } + if (!empty($filters['extends']) && is_string($filters['extends'])) { + $contactTypes = CRM_Contact_BAO_ContactType::basicTypes(TRUE); + // "Contact" should include all contact types (Individual, Organization, Household) + if ($filters['extends'] === 'Contact') { + $filters['extends'] = array_merge(['Contact'], $contactTypes); + } + // A contact type (e.g. "Individual") should include "Contact" + elseif (in_array($filters['extends'], $contactTypes, TRUE)) { + $filters['extends'] = ['Contact', $filters['extends']]; + } + } + $allGroups = array_filter($allGroups, function($group) use ($filters) { + foreach ($filters as $filterKey => $filterValue) { + $groupValue = $group[$filterKey] ?? NULL; + // Compare arrays using array_intersect + if (is_array($filterValue) && is_array($groupValue)) { + if (!array_intersect($groupValue, $filterValue)) { + return FALSE; + } + } + // Compare arrays with scalar using in_array + elseif (is_array($filterValue)) { + if (!in_array($groupValue, $filterValue)) { + return FALSE; + } + } + elseif (is_array($groupValue)) { + if (!in_array($filterValue, $groupValue)) { + return FALSE; + } + } + // Compare scalar values with == + elseif ($groupValue != $filterValue) { + return FALSE; + } + } + return TRUE; + }); + // The `is_active` filter applies to fields as well as groups. + if (!empty($filters['is_active'])) { + foreach ($allGroups as $groupIndex => $group) { + $allGroups[$groupIndex]['fields'] = array_filter($group['fields'], fn($field) => $field['is_active']); + } + } + return $allGroups; + } + + /** + * Fetch all custom groups and fields in a nested array. + * + * Output includes all custom group data + fields. + * + * @return array[] + */ + private static function loadAll(): array { + $cacheString = __CLASS__ . __FUNCTION__ . '_' . CRM_Core_I18n::getLocale(); + $custom = Civi::cache('metadata')->get($cacheString); + if (!isset($custom)) { + $custom = []; + $select = ['g.*']; + foreach (array_keys(CRM_Core_BAO_CustomField::getSupportedFields()) as $fieldKey) { + $select[] = "f.`$fieldKey` AS `field__$fieldKey`"; + } + // Avoid calling the API to prevent recursion or early-bootstrap issues. + $data = \CRM_Utils_SQL_Select::from('civicrm_custom_group g') + ->join('f', 'LEFT JOIN civicrm_custom_field f ON g.id = f.custom_group_id') + ->select($select) + ->orderBy(['g.weight', 'g.name', 'f.weight', 'f.name']) + ->execute()->fetchAll(); + foreach ($data as $groupData) { + $groupId = (int) $groupData['id']; + $fieldData = CRM_Utils_Array::filterByPrefix($groupData, 'field__'); + if (!isset($custom[$groupId])) { + self::formatFieldValues($groupData); + $groupData['fields'] = []; + $custom[$groupId] = $groupData; + } + if ($fieldData['id']) { + CRM_Core_BAO_CustomField::formatFieldValues($fieldData); + $custom[$groupId]['fields'][$fieldData['id']] = $fieldData; + } + } + Civi::cache('metadata')->set($cacheString, $custom); + } + return $custom; + } + /** * FIXME: This function is too complex because it's trying to handle both api-style inputs and * quickform inputs. Needs to be deprecated and broken up. @@ -45,9 +185,6 @@ public static function create(&$params) { } // create custom group dao, populate fields and then save. $group = new CRM_Core_DAO_CustomGroup(); - if (isset($params['title'])) { - $group->title = $params['title']; - } $extendsChildType = NULL; // lets allow user to pass direct child type value, CRM-6893 @@ -86,19 +223,22 @@ public static function create(&$params) { CRM_Core_DAO::VALUE_SEPARATOR; } } - else { + elseif (empty($params['id'])) { $extendsChildType = 'null'; } $group->extends_entity_column_value = $extendsChildType; - if (isset($params['id'])) { - $oldWeight = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $params['id'], 'weight', 'id'); + // Assign new weight + if (empty($params['id'])) { + $group->weight = CRM_Utils_Weight::updateOtherWeights('CRM_Core_DAO_CustomGroup', 0, $params['weight'] ?? CRM_Utils_Weight::getMax('CRM_Core_DAO_CustomGroup')); } - else { - $oldWeight = 0; + // Update weight + if (isset($params['weight']) && !empty($params['id'])) { + $oldWeight = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $params['id'], 'weight', 'id'); + $group->weight = CRM_Utils_Weight::updateOtherWeights('CRM_Core_DAO_CustomGroup', $oldWeight, $params['weight']); } - $group->weight = CRM_Utils_Weight::updateOtherWeights('CRM_Core_DAO_CustomGroup', $oldWeight, CRM_Utils_Array::value('weight', $params, FALSE)); $fields = [ + 'title', 'style', 'collapse_display', 'collapse_adv_display', @@ -207,12 +347,7 @@ public static function create(&$params) { // reset the cache CRM_Utils_System::flushCache(); - if ($tableName) { - CRM_Utils_Hook::post('create', 'CustomGroup', $group->id, $group); - } - else { - CRM_Utils_Hook::post('edit', 'CustomGroup', $group->id, $group); - } + CRM_Utils_Hook::post($op, 'CustomGroup', $group->id, $group); return $group; } @@ -234,7 +369,7 @@ public static function retrieve($params, &$defaults) { */ public static function validateCustomGroupName(CRM_Core_DAO_CustomGroup $group) { $extends = in_array($group->extends, CRM_Contact_BAO_ContactType::basicTypes(TRUE)) ? 'Contact' : $group->extends; - $extendsDAO = CRM_Core_DAO_AllCoreTables::getFullName($extends); + $extendsDAO = CRM_Core_DAO_AllCoreTables::getDAONameForEntity($extends); if ($extendsDAO) { $fields = array_column($extendsDAO::fields(), 'name'); if (in_array($group->name, $fields)) { @@ -264,7 +399,7 @@ public static function setIsActive($id, $is_active) { } /** - * Determine if given entity (sub)type has any custom groups + * @deprecated since 5.71 will be removed around 5.85. * * @param string $extends * E.g. "Individual", "Activity". @@ -276,48 +411,36 @@ public static function setIsActive($id, $is_active) { * @return bool */ public static function hasCustomGroup($extends, $columnId, $columnValue) { + CRM_Core_Error::deprecatedFunctionWarning('CRM_Core_BAO_CustomGroup::getAll'); $dao = new CRM_Core_DAO_CustomGroup(); $dao->extends = $extends; $dao->extends_entity_column_id = $columnId; $escapedValue = CRM_Core_DAO::VALUE_SEPARATOR . CRM_Core_DAO::escapeString($columnValue) . CRM_Core_DAO::VALUE_SEPARATOR; $dao->whereAdd("extends_entity_column_value LIKE \"%$escapedValue%\""); - //$dao->extends_entity_column_value = $columnValue; return (bool) $dao->find(); } /** - * Determine if there are any CustomGroups for the given $activityTypeId. - * If none found, create one. + * @deprecated Function moved * * @param int $activityTypeId - * - * @return bool - * TRUE if a group is found or created; FALSE on error */ public static function autoCreateByActivityType($activityTypeId) { - if (self::hasCustomGroup('Activity', NULL, $activityTypeId)) { - return TRUE; - } - // everything - $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'label', TRUE, FALSE); - $params = [ - 'version' => 3, - 'extends' => 'Activity', - 'extends_entity_column_id' => NULL, - 'extends_entity_column_value' => CRM_Utils_Array::implodePadded([$activityTypeId]), - 'title' => ts('%1 Questions', [1 => $activityTypes[$activityTypeId]]), - 'style' => 'Inline', - 'is_active' => 1, - ]; - $result = civicrm_api('CustomGroup', 'create', $params); - return !$result['is_error']; + CRM_Core_Error::deprecatedFunctionWarning('CRM_Campaign_Form_Survey_Questions::autoCreateCustomGroup'); + return CRM_Campaign_Form_Survey_Questions::autoCreateCustomGroup($activityTypeId); } /** - * Get custom groups/fields data for type of entity in a tree structure representing group->field hierarchy - * This may also include entity specific data values. + * @deprecated Function demonstrates just how bad code can get from 20 years of entropy. * - * An array containing all custom groups and their custom fields is returned. + * This function takes an overcomplicated set of params and returns an overcomplicated + * mix of custom groups, custom fields, custom values (if passed $entityID), and other random stuff. + * + * @see CRM_Core_BAO_CustomGroup::getAll() + * for a better alternative to fetching a tree of custom groups and fields. + * + * @see APIv4::get() + * for a better alternative to fetching entity values. * * @param string $entityType * Of the contact whose contact type is needed. @@ -339,18 +462,12 @@ public static function autoCreateByActivityType($activityTypeId) { * holds 'new' or id if view/edit/copy form for a single record is being loaded. * @param bool $showPublicOnly * - * @return array - * Custom field 'tree'. - * + * @return array[] * The returned array is keyed by group id and has the custom group table fields * and a subkey 'fields' holding the specific custom fields. * If entityId is passed in the fields keys have a subkey 'customValue' which holds custom data * if set for the given entity. This is structured as an array of values with each one having the keys 'id', 'data' * - * @todo - review this - It also returns an array called 'info' with tables, select, from, where keys - * The reason for the info array in unclear and it could be determined from parsing the group tree after creation - * With caching the performance impact would be small & the function would be cleaner - * * @throws \CRM_Core_Exception */ public static function getTree( @@ -367,8 +484,8 @@ public static function getTree( $singleRecord = NULL, $showPublicOnly = FALSE ) { - if ($checkPermission === TRUE) { - CRM_Core_Error::deprecatedWarning('Unexpected TRUE passed to CustomGroup::getTree $checkPermission param.'); + if ($checkPermission && !in_array($checkPermission, [CRM_Core_Permission::EDIT, CRM_Core_Permission::VIEW], TRUE)) { + CRM_Core_Error::deprecatedWarning("Unexpected value '$checkPermission' passed to CustomGroup::getTree \$checkPermission param."); $checkPermission = CRM_Core_Permission::EDIT; } if ($entityID) { @@ -388,187 +505,42 @@ public static function getTree( } } - // create a new tree - - // legacy hardcoded list of data to return - $tableData = [ - 'custom_field' => [ - 'id', - 'name', - 'label', - 'column_name', - 'data_type', - 'html_type', - 'default_value', - 'attributes', - 'is_required', - 'is_view', - 'help_pre', - 'help_post', - 'options_per_line', - 'start_date_years', - 'end_date_years', - 'date_format', - 'time_format', - 'option_group_id', - 'in_selector', - ], - 'custom_group' => [ - 'id', - 'name', - 'table_name', - 'title', - 'help_pre', - 'help_post', - 'collapse_display', - 'style', - 'is_multiple', - 'extends', - 'extends_entity_column_id', - 'extends_entity_column_value', - 'max_multiple', - 'is_public', - ], - ]; - $current_db_version = CRM_Core_BAO_Domain::version(); - $serialize_version = version_compare($current_db_version, '5.27.alpha1', '>='); - if ($serialize_version) { - $tableData['custom_field'][] = 'serialize'; - } - if (!$toReturn || !is_array($toReturn)) { - $toReturn = $tableData; - } - else { - // Supply defaults and remove unknown array keys - $toReturn = array_intersect_key(array_filter($toReturn) + $tableData, $tableData); - // Merge in required fields that we must have - $toReturn['custom_field'] = array_unique(array_merge($toReturn['custom_field'], ['id', 'column_name', 'data_type'])); - $toReturn['custom_group'] = array_unique(array_merge($toReturn['custom_group'], ['id', 'is_multiple', 'table_name', 'name'])); - // Validate return fields - $toReturn['custom_field'] = array_intersect($toReturn['custom_field'], array_keys(CRM_Core_DAO_CustomField::fieldKeys())); - $toReturn['custom_group'] = array_intersect($toReturn['custom_group'], array_keys(CRM_Core_DAO_CustomGroup::fieldKeys())); - } - - // create select - $select = []; - foreach ($toReturn as $tableName => $tableColumn) { - foreach ($tableColumn as $columnName) { - $select[] = "civicrm_{$tableName}.{$columnName} as civicrm_{$tableName}_{$columnName}"; - } - } - $strSelect = "SELECT " . implode(', ', $select); - - // from, where, order by - $strFrom = " -FROM civicrm_custom_group -LEFT JOIN civicrm_custom_field ON (civicrm_custom_field.custom_group_id = civicrm_custom_group.id) -"; - - // if entity is either individual, organization or household pls get custom groups for 'contact' too. - if ($entityType == "Individual" || $entityType == 'Organization' || - $entityType == 'Household' - ) { - $in = "'$entityType', 'Contact'"; - } - elseif (strpos($entityType, "'") !== FALSE) { - // this allows the calling function to send in multiple entity types - $in = $entityType; - } - else { - // quote it - $in = "'$entityType'"; + if (str_contains($entityType, "'")) { + // Handle really weird legacy input format + $entityType = explode(',', str_replace([' ', "'"], '', $entityType)); } - $params = []; - $sqlParamKey = 1; - $subType = ''; - if (!empty($subTypes)) { - foreach ($subTypes as $key => $subType) { - $subTypeClauses[] = self::whereListHas("civicrm_custom_group.extends_entity_column_value", self::validateSubTypeByEntity($entityType, $subType)); + $filters = [ + 'extends' => $entityType, + 'is_active' => TRUE, + ]; + if ($subTypes) { + foreach ($subTypes as $subType) { + $filters['extends_entity_column_value'][] = self::validateSubTypeByEntity($entityType, $subType); } - $subTypeClause = '(' . implode(' OR ', $subTypeClauses) . ')'; if (!$onlySubType) { - $subTypeClause = '(' . $subTypeClause . ' OR civicrm_custom_group.extends_entity_column_value IS NULL )'; + $filters['extends_entity_column_value'][] = NULL; } - - $strWhere = " -WHERE civicrm_custom_group.is_active = 1 - AND civicrm_custom_field.is_active = 1 - AND civicrm_custom_group.extends IN ($in) - AND $subTypeClause -"; if ($subName) { - $strWhere .= " AND civicrm_custom_group.extends_entity_column_id = %{$sqlParamKey}"; - $params[$sqlParamKey] = [$subName, 'String']; - $sqlParamKey = $sqlParamKey + 1; + $filters['extends_entity_column_id'] = $subName; } } - else { - $strWhere = " -WHERE civicrm_custom_group.is_active = 1 - AND civicrm_custom_field.is_active = 1 - AND civicrm_custom_group.extends IN ($in) -"; - if (!$returnAll) { - $strWhere .= "AND civicrm_custom_group.extends_entity_column_value IS NULL"; - } + elseif (!$returnAll) { + $filters['extends_entity_column_value'] = NULL; } - if ($groupID > 0) { - // since we want a specific group id we add it to the where clause - $strWhere .= " AND civicrm_custom_group.id = %{$sqlParamKey}"; - $params[$sqlParamKey] = [$groupID, 'Integer']; + $filters['id'] = $groupID; } elseif (!$groupID) { // since groupID is false we need to show all Inline groups - $strWhere .= " AND civicrm_custom_group.style = 'Inline'"; + $filters['style'] = 'Inline'; } - if ($checkPermission) { - // ensure that the user has access to these custom groups - $strWhere .= " AND " . - CRM_Core_Permission::customGroupClause($checkPermission, - 'civicrm_custom_group.' - ); - } - if ($showPublicOnly) { - $strWhere .= 'AND civicrm_custom_group.is_public = 1'; - } - - $orderBy = ' -ORDER BY civicrm_custom_group.weight, - civicrm_custom_group.title, - civicrm_custom_field.weight, - civicrm_custom_field.label -'; - - // final query string - $queryString = "$strSelect $strFrom $strWhere $orderBy"; - - // lets see if we can retrieve the groupTree from cache - $cacheString = $queryString; - if ($groupID > 0) { - $cacheString .= "_{$groupID}"; - } - else { - $cacheString .= "_Inline"; + $filters['is_public'] = TRUE; } - $multipleFieldGroups = []; - $cacheKey = "CRM_Core_DAO_CustomGroup_Query " . md5($cacheString); - $multipleFieldGroupCacheKey = "CRM_Core_DAO_CustomGroup_QueryMultipleFields " . md5($cacheString); - $cache = CRM_Utils_Cache::singleton(); - if ($fromCache) { - $groupTree = $cache->get($cacheKey); - $multipleFieldGroups = $cache->get($multipleFieldGroupCacheKey); - } + [$multipleFieldGroups, $groupTree] = self::buildLegacyGroupTree($filters, $checkPermission, $subTypes); - if (empty($groupTree)) { - [$multipleFieldGroups, $groupTree] = self::buildGroupTree($entityType, $toReturn, $subTypes, $queryString, $params, $subType); - - $cache->set($cacheKey, $groupTree); - $cache->set($multipleFieldGroupCacheKey, $multipleFieldGroups); - } // entitySelectClauses is an array of select clauses for custom value tables which are not multiple // and have data for the given entities. $entityMultipleSelectClauses is the same for ones with multiple $entitySingleSelectClauses = $entityMultipleSelectClauses = $groupTree['info']['select'] = []; @@ -576,11 +548,7 @@ public static function getTree( // now that we have all the groups and fields, lets get the values // since we need to know the table and field names // add info to groupTree - - if (isset($groupTree['info']) && !empty($groupTree['info']) && - !empty($groupTree['info']['tables']) && $singleRecord != 'new' - ) { - $select = $from = $where = []; + if (!empty($groupTree['info']['tables']) && $singleRecord != 'new') { $groupTree['info']['where'] = NULL; foreach ($groupTree['info']['tables'] as $table => $fields) { @@ -620,9 +588,62 @@ public static function getTree( } /** - * Clean and validate the filter before it is used in a db query. + * Recreates legacy formatting for getTree but uses the new cached function to retrieve data. + * @deprecated only used by legacy function. + */ + private static function buildLegacyGroupTree($filters, $permission, $subTypes) { + $multipleFieldGroups = []; + $customValueTables = []; + $customGroups = self::getAll($filters, $permission ?: NULL); + foreach ($customGroups as &$group) { + self::formatLegacyDbValues($group); + if ($group['is_multiple']) { + $multipleFieldGroups[$group['id']] = $group['table_name']; + } + // CRM-5507 - Hard to know what this was supposed to do but this faithfully recreates + // whatever it was doing before the refactor, which was probably broken anyway. + if (!empty($subTypes[0])) { + $group['subtype'] = self::validateSubTypeByEntity(CRM_Utils_Array::first((array) $filters['extends']), $subTypes[0]); + } + foreach ($group['fields'] as &$field) { + self::formatLegacyDbValues($field); + $customValueTables[$group['table_name']][$field['column_name']] = 1; + } + } + $customGroups['info'] = ['tables' => $customValueTables]; + return [$multipleFieldGroups, $customGroups]; + } + + /** + * Recreates the crude string-only format originally produced by self::getTree. + * @deprecated only used by legacy functions. + */ + private static function formatLegacyDbValues(array &$values): void { + foreach ($values as $key => $value) { + if ($key === 'fields') { + continue; + } + if (is_null($value)) { + unset($values[$key]); + continue; + } + if (is_bool($value)) { + $value = (int) $value; + } + if (is_array($value)) { + $value = CRM_Utils_Array::implodePadded($value); + } + $values[$key] = (string) $value; + } + } + + /** + * Validates contact subtypes and event types. * - * @internal this will be private again soon. + * Performs case-insensitive matching of strings and outputs the correct case. + * e.g. an input of "meeting" would output "Meeting". + * + * For all other entities, it doesn't validate except to check the subtype is an integer. * * @param string $entityType * @param string $subType @@ -630,48 +651,39 @@ public static function getTree( * @return string * @throws \CRM_Core_Exception */ - public static function validateSubTypeByEntity($entityType, $subType) { + private static function validateSubTypeByEntity($entityType, $subType) { $subType = trim($subType, CRM_Core_DAO::VALUE_SEPARATOR); if (is_numeric($subType)) { return $subType; } $contactTypes = CRM_Contact_BAO_ContactType::basicTypeInfo(TRUE); - $contactTypes = array_merge($contactTypes, ['Event' => 1]); + $contactTypes['Contact'] = 1; - if ($entityType != 'Contact' && !array_key_exists($entityType, $contactTypes)) { + if ($entityType === 'Event') { + $subTypes = CRM_Core_OptionGroup::values('event_type', TRUE, FALSE, FALSE, NULL, 'name'); + } + elseif (!array_key_exists($entityType, $contactTypes)) { throw new CRM_Core_Exception('Invalid Entity Filter'); } - $subTypes = CRM_Contact_BAO_ContactType::subTypeInfo($entityType, TRUE); - $subTypes = array_merge($subTypes, CRM_Event_PseudoConstant::eventType()); + else { + $subTypes = CRM_Contact_BAO_ContactType::subTypeInfo($entityType, TRUE); + $subTypes = array_column($subTypes, 'name', 'name'); + } // When you create a new contact type it gets saved in mixed case in the database. // Eg. "Service User" becomes "Service_User" in civicrm_contact_type.name // But that field does not differentiate case (eg. you can't add Service_User and service_user because mysql will report a duplicate error) // webform_civicrm and some other integrations pass in the name as lowercase to API3 Contact.duplicatecheck // Since we can't actually have two strings with different cases in the database perform a case-insensitive search here: - $subTypes = array_change_key_case($subTypes, CASE_LOWER); - if (!array_key_exists(mb_strtolower($subType), $subTypes)) { + $subTypesByName = array_combine($subTypes, $subTypes); + $subTypesByName = array_change_key_case($subTypesByName, CASE_LOWER); + $subTypesByKey = array_change_key_case($subTypes, CASE_LOWER); + $subTypeKey = mb_strtolower($subType); + if (!array_key_exists($subTypeKey, $subTypesByKey) && !in_array($subTypeKey, $subTypesByName)) { \Civi::log()->debug("entityType: {$entityType}; subType: {$subType}"); throw new CRM_Core_Exception('Invalid Filter'); } - return $subType; - } - - /** - * Suppose you have a SQL column, $column, which includes a delimited list, and you want - * a WHERE condition for rows that include $value. Use whereListHas(). - * - * @param string $column - * @param string $value - * @param string $delimiter - * @return string - * SQL condition. - */ - private static function whereListHas($column, $value, $delimiter = CRM_Core_DAO::VALUE_SEPARATOR) { - // ? - $bareValue = trim($value, $delimiter); - $escapedValue = CRM_Utils_Type::escape("%{$delimiter}{$bareValue}{$delimiter}%", 'String', FALSE); - return "($column LIKE \"$escapedValue\")"; + return $subTypesByName[$subTypeKey] ?? $subTypesByKey[$subTypeKey]; } /** @@ -923,13 +935,14 @@ public static function buildCustomFieldData($dao, &$groupTree, $table, $groupID, * title */ public static function getTitle($id) { - return CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $id, 'title'); + return self::getAll()[$id]['title'] ?? NULL; } /** - * Get custom group details for a group. + * @deprecated Legacy function * - * An array containing custom group details (including their custom field) is returned. + * @see CRM_Core_BAO_CustomGroup::getAll() + * for a better alternative. * * @param int $groupId * Group id whose details are needed. @@ -937,146 +950,48 @@ public static function getTitle($id) { * Is this field searchable. * @param array $extends * Which table does it extend if any. - * * @param bool $inSelector * * @return array * array consisting of all group and field details */ public static function &getGroupDetail($groupId = NULL, $searchable = NULL, &$extends = NULL, $inSelector = NULL) { - // create a new tree - $groupTree = []; - - // using tableData to build the queryString - $tableData = [ - 'civicrm_custom_field' => [ - 'id', - 'label', - 'data_type', - 'html_type', - 'default_value', - 'attributes', - 'is_required', - 'help_pre', - 'help_post', - 'options_per_line', - 'is_searchable', - 'start_date_years', - 'end_date_years', - 'is_search_range', - 'date_format', - 'time_format', - 'note_columns', - 'note_rows', - 'column_name', - 'is_view', - 'option_group_id', - 'in_selector', - ], - 'civicrm_custom_group' => [ - 'id', - 'name', - 'title', - 'help_pre', - 'help_post', - 'collapse_display', - 'collapse_adv_display', - 'extends', - 'extends_entity_column_value', - 'table_name', - 'is_multiple', - ], + $groupFilters = [ + 'is_active' => TRUE, ]; - - // create select - $s = []; - foreach ($tableData as $tableName => $tableColumn) { - foreach ($tableColumn as $columnName) { - $s[] = "{$tableName}.{$columnName} as {$tableName}_{$columnName}"; - } - } - $select = 'SELECT ' . implode(', ', $s); - $params = []; - // from, where, order by - $from = " FROM civicrm_custom_field, civicrm_custom_group"; - $where = " WHERE civicrm_custom_field.custom_group_id = civicrm_custom_group.id - AND civicrm_custom_group.is_active = 1 - AND civicrm_custom_field.is_active = 1 "; + $fieldFilters = []; if ($groupId) { - $params[1] = [$groupId, 'Integer']; - $where .= " AND civicrm_custom_group.id = %1"; + $groupFilters['id'] = $groupId; } - if ($searchable) { - $where .= " AND civicrm_custom_field.is_searchable = 1"; + $fieldFilters['is_searchable'] = TRUE; } - if ($inSelector) { - $where .= " AND civicrm_custom_field.in_selector = 1 AND civicrm_custom_group.is_multiple = 1 "; + $groupFilters['is_multiple'] = TRUE; + $fieldFilters['in_selector'] = TRUE; } - if ($extends) { - $clause = []; - foreach ($extends as $e) { - $clause[] = "civicrm_custom_group.extends = '$e'"; - } - $where .= " AND ( " . implode(' OR ', $clause) . " ) "; + $groupFilters['extends'] = $extends; //include case activities customdata if case is enabled if (in_array('Activity', $extends)) { - $extendValues = implode(',', array_keys(CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'label', TRUE))); - $where .= " AND ( civicrm_custom_group.extends_entity_column_value IS NULL OR REPLACE( civicrm_custom_group.extends_entity_column_value, %2, ' ') IN ($extendValues) ) "; - $params[2] = [CRM_Core_DAO::VALUE_SEPARATOR, 'String']; + $extendValues = array_keys(CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'label', TRUE)); + $extendValues[] = NULL; + $groupFilters['extends_entity_column_value'] = $extendValues; } } // ensure that the user has access to these custom groups - $where .= " AND " . - CRM_Core_Permission::customGroupClause(CRM_Core_Permission::VIEW, - 'civicrm_custom_group.' - ); - - $orderBy = " ORDER BY civicrm_custom_group.weight, civicrm_custom_field.weight"; - - // final query string - $queryString = $select . $from . $where . $orderBy; - - // dummy dao needed - $crmDAO = CRM_Core_DAO::executeQuery($queryString, $params); + $groupTree = self::getAll($groupFilters, CRM_Core_Permission::VIEW); // process records - while ($crmDAO->fetch()) { - $groupId = $crmDAO->civicrm_custom_group_id; - $fieldId = $crmDAO->civicrm_custom_field_id; - - // create an array for groups if it does not exist - if (!array_key_exists($groupId, $groupTree)) { - $groupTree[$groupId] = []; - $groupTree[$groupId]['id'] = $groupId; - - foreach ($tableData['civicrm_custom_group'] as $v) { - $fullField = "civicrm_custom_group_" . $v; - - if ($v == 'id' || is_null($crmDAO->$fullField)) { - continue; - } - - $groupTree[$groupId][$v] = $crmDAO->$fullField; - } - - $groupTree[$groupId]['fields'] = []; + foreach ($groupTree as &$group) { + self::formatLegacyDbValues($group); + foreach ($group['fields'] as &$field) { + self::formatLegacyDbValues($field); } - - // add the fields now (note - the query row will always contain a field) - $groupTree[$groupId]['fields'][$fieldId] = []; - $groupTree[$groupId]['fields'][$fieldId]['id'] = $fieldId; - - foreach ($tableData['civicrm_custom_field'] as $v) { - $fullField = "civicrm_custom_field_" . $v; - if ($v == 'id' || is_null($crmDAO->$fullField)) { - continue; - } - $groupTree[$groupId]['fields'][$fieldId][$v] = $crmDAO->$fullField; + if ($fieldFilters) { + $group['fields'] = array_column(CRM_Utils_Array::findAll($group['fields'], $fieldFilters), NULL, 'id'); } } @@ -1084,11 +999,7 @@ public static function &getGroupDetail($groupId = NULL, $searchable = NULL, &$ex } /** - * @param $entityType - * @param $path - * @param string $cidToken - * - * @return array + * @deprecated since 5.71, will be removed around 5.85 */ public static function &getActiveGroups($entityType, $path, $cidToken = '%%cid%%') { // for Group's @@ -1098,7 +1009,7 @@ public static function &getActiveGroups($entityType, $path, $cidToken = '%%cid%% $customGroupDAO->whereAdd("style IN ('Tab', 'Tab with table')"); $customGroupDAO->whereAdd("is_active = 1"); - // add whereAdd for entity type + // Emits a noisy deprecation notice self::_addWhereAdd($customGroupDAO, $entityType, $cidToken); $groups = []; @@ -1128,19 +1039,11 @@ public static function &getActiveGroups($entityType, $path, $cidToken = '%%cid%% } /** - * Get the table name for the entity type - * currently if entity type is 'Contact', 'Individual', 'Household', 'Organization' - * tableName is 'civicrm_contact' - * - * @param string $entityType - * What entity are we extending here ?. - * - * @return string - * - * - * @see _apachesolr_civiAttachments_dereference_file_parent + * Unused function. + * @deprecated since 5.71 will be removed around 5.85 */ public static function getTableNameByEntityName($entityType) { + CRM_Core_Error::deprecatedFunctionWarning('CoreUtil::getTableName'); switch ($entityType) { case 'Contact': case 'Individual': @@ -1154,9 +1057,7 @@ public static function getTableNameByEntityName($entityType) { } /** - * Get a list of custom groups which extend a given entity type. - * If there are custom-groups which only apply to certain subtypes, - * those WILL be included. + * @deprecated since 5.71 will be removed around 5.85 * * @param string $entityType * @@ -1164,22 +1065,16 @@ public static function getTableNameByEntityName($entityType) { */ public static function getAllCustomGroupsByBaseEntity($entityType) { $customGroupDAO = new CRM_Core_DAO_CustomGroup(); + // Emits a noisy deprecation notice self::_addWhereAdd($customGroupDAO, $entityType, NULL, TRUE); return $customGroupDAO; } /** - * Add the whereAdd clause for the DAO depending on the type of entity - * the custom group is extending. - * - * @param object $customGroupDAO - * @param string $entityType - * What entity are we extending here ?. - * - * @param int $entityID - * @param bool $allSubtypes + * @deprecated since 5.71 will be removed around 5.85 */ private static function _addWhereAdd(&$customGroupDAO, $entityType, $entityID = NULL, $allSubtypes = FALSE) { + CRM_Core_Error::deprecatedFunctionWarning('CRM_Core_BAO_CustomGroup::getAll'); $addSubtypeClause = FALSE; // This function isn't really accessible with user data but since the string // is not passed as a param to the query CRM_Core_DAO::escapeString seems like a harmless @@ -1296,8 +1191,8 @@ public static function deleteRecord(array $record) { * @param bool $inactiveNeeded * @param int $action */ - public static function setDefaults(&$groupTree, &$defaults, $viewMode = FALSE, $inactiveNeeded = FALSE, $action = CRM_Core_Action::NONE) { - foreach ($groupTree as $id => $group) { + public static function setDefaults($groupTree, &$defaults, $viewMode = FALSE, $inactiveNeeded = FALSE, $action = CRM_Core_Action::NONE) { + foreach ($groupTree as $group) { if (!isset($group['fields'])) { continue; } @@ -1364,6 +1259,11 @@ public static function setDefaults(&$groupTree, &$defaults, $viewMode = FALSE, $ } } else { + if ($field['html_type'] === 'Autocomplete-Select') { + $checkedValue = array_filter((array) \CRM_Utils_Array::explodePadded($value)); + $defaults[$elementName] = implode(',', $checkedValue); + continue; + } // Values may be "array strings" or actual arrays. Handle both. if (is_array($value) && count($value)) { CRM_Utils_Array::formatArrayKeys($value); @@ -1374,7 +1274,7 @@ public static function setDefaults(&$groupTree, &$defaults, $viewMode = FALSE, $ } foreach ($customOption as $val) { if (in_array($val['value'], $checkedValue)) { - if ($field['html_type'] == 'CheckBox') { + if ($field['html_type'] === 'CheckBox') { $defaults[$elementName][$val['value']] = 1; } else { @@ -1443,7 +1343,7 @@ public static function setDefaults(&$groupTree, &$defaults, $viewMode = FALSE, $ } /** - * Old function only called from one place... + * @deprecated since 5.71 will be remvoed around 5.77 * @see CRM_Dedupe_Finder::formatParams * * @param array $groupTree @@ -1451,6 +1351,7 @@ public static function setDefaults(&$groupTree, &$defaults, $viewMode = FALSE, $ * @param bool $skipFile */ public static function postProcess(&$groupTree, &$params, $skipFile = FALSE) { + CRM_Core_Error::deprecatedFunctionWarning('no alternative'); // Get the Custom form values and groupTree foreach ($groupTree as $groupID => $group) { if ($groupID === 'info') { @@ -1554,8 +1455,8 @@ public static function postProcess(&$groupTree, &$params, $skipFile = FALSE) { * * @throws \CRM_Core_Exception */ - public static function buildQuickForm(&$form, &$groupTree, $inactiveNeeded = FALSE, $prefix = '') { - $form->assign_by_ref("{$prefix}groupTree", $groupTree); + public static function buildQuickForm($form, $groupTree, $inactiveNeeded = FALSE, $prefix = '') { + $form->assign("{$prefix}groupTree", $groupTree); foreach ($groupTree as $id => $group) { foreach ($group['fields'] as $field) { @@ -1669,32 +1570,19 @@ public static function extractGetParams(&$form, $type) { } /** - * Check the type of custom field type (eg: Used for Individual, Contribution, etc) - * this function is used to get the custom fields of a type (eg: Used for Individual, Contribution, etc ) + * @deprecated Silly function that does almost nothing. + * @see CRM_Core_BAO_CustomField::getField() + * for a more useful alternative. * * @param int $customFieldId - * Custom field id. * @param array $removeCustomFieldTypes * Remove custom fields of a type eg: array("Individual") ;. * * @return bool - * false if it matches else true - * - * @throws \CRM_Core_Exception */ public static function checkCustomField($customFieldId, $removeCustomFieldTypes) { - $query = 'SELECT cg.extends as extends - FROM civicrm_custom_group as cg, civicrm_custom_field as cf - WHERE cg.id = cf.custom_group_id - AND cf.id =' . - CRM_Utils_Type::escape($customFieldId, 'Integer'); - - $extends = CRM_Core_DAO::singleValueQuery($query); - - if (in_array($extends, $removeCustomFieldTypes)) { - return FALSE; - } - return TRUE; + $extends = CRM_Core_BAO_CustomField::getField($customFieldId)['custom_group']['extends']; + return !in_array($extends, $removeCustomFieldTypes); } /** @@ -1816,14 +1704,7 @@ public static function formatGroupTree($groupTree, $groupCount = 1, &$form = NUL $form->assign('qfKey', $qf); Civi::cache('customData')->set($qf, $formValues); } - - // hack for field type File - $formUploadNames = $form->get('uploadNames'); - if (is_array($formUploadNames)) { - $uploadNames = array_unique(array_merge($formUploadNames, $uploadNames)); - } - - $form->set('uploadNames', $uploadNames); + $form->registerFileField($uploadNames); } return $formattedGroupTree; @@ -1944,39 +1825,30 @@ public static function buildCustomDataView($form, $groupTree, $returnCount = FAL } /** - * Get the custom group titles by custom field ids. + * @deprecated Silly function that shouldn't exist. + * + * @see CRM_Core_BAO_CustomField::getField() + * for a better alternative. * * @param array $fieldIds * Array of custom field ids. * - * @return array|NULL + * @return array * array consisting of groups and fields labels with ids. */ - public static function getGroupTitles($fieldIds) { - if (!is_array($fieldIds) && empty($fieldIds)) { - return NULL; - } - + public static function getGroupTitles(array $fieldIds): array { $groupLabels = []; - $fIds = "(" . implode(',', $fieldIds) . ")"; - - $query = " -SELECT civicrm_custom_group.id as groupID, civicrm_custom_group.title as groupTitle, - civicrm_custom_field.label as fieldLabel, civicrm_custom_field.id as fieldID - FROM civicrm_custom_group, civicrm_custom_field - WHERE civicrm_custom_group.id = civicrm_custom_field.custom_group_id - AND civicrm_custom_field.id IN {$fIds}"; - - $dao = CRM_Core_DAO::executeQuery($query); - while ($dao->fetch()) { - $groupLabels[$dao->fieldID] = [ - 'fieldID' => $dao->fieldID, - 'fieldLabel' => $dao->fieldLabel, - 'groupID' => $dao->groupID, - 'groupTitle' => $dao->groupTitle, - ]; + foreach ($fieldIds as $fieldId) { + $field = CRM_Core_BAO_CustomField::getField($fieldId); + if ($field) { + $groupLabels[$fieldId] = [ + 'fieldID' => (string) $fieldId, + 'fieldLabel' => $field['label'], + 'groupID' => (string) $field['custom_group']['id'], + 'groupTitle' => $field['custom_group']['title'], + ]; + } } - return $groupLabels; } @@ -2203,35 +2075,22 @@ public static function buildOptions($fieldName, $context = NULL, $props = []) { } /** + * Returns TRUE if this is a multivalued group which has reached the max for a given entity. + * * @param int $customGroupId * @param int $entityId - * - * @return bool */ - public static function hasReachedMaxLimit($customGroupId, $entityId) { - // check whether the group is multiple - $isMultiple = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $customGroupId, 'is_multiple'); - $isMultiple = (bool) $isMultiple; - $hasReachedMax = FALSE; - if ($isMultiple && - ($maxMultiple = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $customGroupId, 'max_multiple')) - ) { - if (!$maxMultiple) { - $hasReachedMax = FALSE; - } - else { - $tableName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $customGroupId, 'table_name'); - // count the number of entries for a entity - $sql = "SELECT COUNT(id) FROM {$tableName} WHERE entity_id = %1"; - $params = [1 => [$entityId, 'Integer']]; - $count = CRM_Core_DAO::singleValueQuery($sql, $params); - - if ($count >= $maxMultiple) { - $hasReachedMax = TRUE; - } - } - } - return $hasReachedMax; + public static function hasReachedMaxLimit($customGroupId, $entityId): bool { + $customGroup = self::getGroup(['id' => $customGroupId]); + $maxMultiple = $customGroup['max_multiple'] ?? NULL; + if ($maxMultiple && $customGroup['is_multiple']) { + // count the number of entries for entity + $sql = "SELECT COUNT(id) FROM {$customGroup['table_name']} WHERE entity_id = %1"; + $params = [1 => [$entityId, 'Integer']]; + $count = CRM_Core_DAO::singleValueQuery($sql, $params); + return $count >= $maxMultiple; + } + return FALSE; } /** @@ -2250,93 +2109,13 @@ public static function getMultipleFieldGroup() { return $multipleGroup; } - /** - * Build the metadata tree for the custom group. - * - * @internal - function is temporarily public but will be private again - * once separated function disentangled. - * - * @param string $entityType - * @param array $toReturn - * @param array $subTypes - * @param string $queryString - * @param array $params - * @param string $subType - * - * @return array - * @throws \CRM_Core_Exception - */ - public static function buildGroupTree($entityType, $toReturn, $subTypes, $queryString, $params, $subType) { - $groupTree = $multipleFieldGroups = []; - $crmDAO = CRM_Core_DAO::executeQuery($queryString, $params); - $customValueTables = []; - - // process records - while ($crmDAO->fetch()) { - // get the id's - $groupID = $crmDAO->civicrm_custom_group_id; - $fieldId = $crmDAO->civicrm_custom_field_id; - if ($crmDAO->civicrm_custom_group_is_multiple) { - $multipleFieldGroups[$groupID] = $crmDAO->civicrm_custom_group_table_name; - } - // create an array for groups if it does not exist - if (!array_key_exists($groupID, $groupTree)) { - $groupTree[$groupID] = []; - $groupTree[$groupID]['id'] = $groupID; - - // populate the group information - foreach ($toReturn['custom_group'] as $fieldName) { - $fullFieldName = "civicrm_custom_group_$fieldName"; - if ($fieldName == 'id' || - is_null($crmDAO->$fullFieldName) - ) { - continue; - } - // CRM-5507 - // This is an old bit of code - per the CRM number & probably does not work reliably if - // that one contact sub-type exists. - if ($fieldName == 'extends_entity_column_value' && !empty($subTypes[0])) { - $groupTree[$groupID]['subtype'] = self::validateSubTypeByEntity($entityType, $subType); - } - $groupTree[$groupID][$fieldName] = $crmDAO->$fullFieldName; - } - $groupTree[$groupID]['fields'] = []; - - $customValueTables[$crmDAO->civicrm_custom_group_table_name] = []; - } - - // add the fields now (note - the query row will always contain a field) - // we only reset this once, since multiple values come is as multiple rows - if (!array_key_exists($fieldId, $groupTree[$groupID]['fields'])) { - $groupTree[$groupID]['fields'][$fieldId] = []; - } - - $customValueTables[$crmDAO->civicrm_custom_group_table_name][$crmDAO->civicrm_custom_field_column_name] = 1; - $groupTree[$groupID]['fields'][$fieldId]['id'] = $fieldId; - // populate information for a custom field - foreach ($toReturn['custom_field'] as $fieldName) { - $fullFieldName = "civicrm_custom_field_$fieldName"; - if ($fieldName == 'id' || - is_null($crmDAO->$fullFieldName) - ) { - continue; - } - $groupTree[$groupID]['fields'][$fieldId][$fieldName] = $crmDAO->$fullFieldName; - } - } - - if (!empty($customValueTables)) { - $groupTree['info'] = ['tables' => $customValueTables]; - } - return [$multipleFieldGroups, $groupTree]; - } - /** * Use APIv4 getFields (or self::getExtendsEntityColumnValueOptions) instead of this beast. - * @deprecated + * @deprecated as of 5.72 use getExtendsEntityColumnValueOptions - will be removed by 5.78 * @return array */ public static function getSubTypes(): array { + CRM_Core_Error::deprecatedFunctionWarning('CRM_Core_BAO_CustomGroup::getExtendsEntityColumnValueOptions'); $sel2 = []; $activityType = CRM_Activity_BAO_Activity::buildOptions('activity_type_id', 'search'); diff --git a/www/modules/civicrm/CRM/Core/BAO/CustomValue.php b/www/modules/civicrm/CRM/Core/BAO/CustomValue.php index 8b965803d..f9537ee37 100644 --- a/www/modules/civicrm/CRM/Core/BAO/CustomValue.php +++ b/www/modules/civicrm/CRM/Core/BAO/CustomValue.php @@ -15,10 +15,12 @@ * @copyright CiviCRM LLC https://civicrm.org/licensing */ +use Civi\Api4\Event\AuthorizeRecordEvent; + /** * Business objects for managing custom data values. */ -class CRM_Core_BAO_CustomValue extends CRM_Core_DAO { +class CRM_Core_BAO_CustomValue extends CRM_Core_DAO implements \Civi\Core\HookInterface { /** * Validate a value against a CustomField type. @@ -192,7 +194,7 @@ public static function fixCustomFieldValue(&$formValues) { */ public static function deleteCustomValue($customValueID, $customGroupID) { // first we need to find custom value table, from custom group ID - $tableName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $customGroupID, 'table_name'); + $tableName = CRM_Core_BAO_CustomGroup::getGroup(['id' => $customGroupID])['table_name']; // Retrieve the $entityId so we can pass that to the hook. $entityID = (int) CRM_Core_DAO::singleValueQuery("SELECT entity_id FROM {$tableName} WHERE id = %1", [ @@ -230,36 +232,28 @@ public function addSelectWhereClause(string $entityName = NULL, int $userId = NU } /** - * Special checkAccess function for multi-record custom pseudo-entities - * - * @param string $entityName - * APIv4-style entity name e.g. 'Custom_Foobar' - * @param string $action - * @param array $record - * @param int $userID - * Contact ID of the active user (whose access we must check). 0 for anonymous. - * @return bool - * TRUE if granted. FALSE if prohibited. NULL if indeterminate. + * Access check for multi-record custom pseudo-entities + * @see \Civi\Api4\Utils\CoreUtil::checkAccessRecord */ - public static function _checkAccess(string $entityName, string $action, array $record, int $userID): ?bool { + public static function on_civi_api4_authorizeRecord(AuthorizeRecordEvent $e): void { + $groupName = \Civi\Api4\Utils\CoreUtil::getCustomGroupName($e->getEntityName()); + if (!$groupName) { + return; + } + // This check implements two rules: you must have access to the specific custom-data-group - and to the underlying record (e.g. Contact). + $record = $e->getRecord(); + $userID = $e->getUserID(); + $action = $e->getActionName(); // Expecting APIv4-style entity name - $groupName = \Civi\Api4\Utils\CoreUtil::getCustomGroupName($entityName); $extends = \CRM_Core_BAO_CustomGroup::getEntityForGroup($groupName); $id = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $groupName, 'id', 'name'); - if (!$groupName) { - // $groupName is required but the function signature has to match the parent. - throw new CRM_Core_Exception('Missing required group-name in CustomValue::checkAccess'); - } - - if (empty($extends) || empty($id)) { - throw new CRM_Core_Exception('Received invalid group-name in CustomValue::checkAccess'); - } $actionType = $action === 'get' ? CRM_Core_Permission::VIEW : CRM_Core_Permission::EDIT; if (!\CRM_Core_BAO_CustomGroup::checkGroupAccess($id, $actionType, $userID)) { - return FALSE; + $e->setAuthorized(FALSE); + return; } $eid = $record['entity_id'] ?? NULL; @@ -270,7 +264,7 @@ public static function _checkAccess(string $entityName, string $action, array $r // Do we have access to the target record? $delegatedAction = $action === 'get' ? 'get' : 'update'; - return \Civi\Api4\Utils\CoreUtil::checkAccessDelegated($extends, $delegatedAction, ['id' => $eid], $userID); + $e->setAuthorized(\Civi\Api4\Utils\CoreUtil::checkAccessDelegated($extends, $delegatedAction, ['id' => $eid], $userID)); } } diff --git a/www/modules/civicrm/CRM/Core/BAO/CustomValueTable.php b/www/modules/civicrm/CRM/Core/BAO/CustomValueTable.php index 1085eb702..97d07a345 100644 --- a/www/modules/civicrm/CRM/Core/BAO/CustomValueTable.php +++ b/www/modules/civicrm/CRM/Core/BAO/CustomValueTable.php @@ -371,8 +371,7 @@ public static function store($params, $entityTable, $entityID, $parentOperation 'table_name' => $customValue['table_name'], 'column_name' => $customValue['column_name'], // is_multiple refers to the custom group, serialize refers to the field. - // @todo is_multiple can be null - does that mean anything different from 0? - 'is_multiple' => $customValue['is_multiple'] ?? CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $customValue['custom_group_id'], 'is_multiple'), + 'is_multiple' => (int) ($customValue['is_multiple'] ?? CRM_Core_BAO_CustomGroup::getGroup(['id' => $customValue['custom_group_id']])['is_multiple']), 'serialize' => $customValue['serialize'] ?? (int) CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', $customValue['custom_field_id'], 'serialize'), 'file_id' => $customValue['file_id'], ]; @@ -580,54 +579,30 @@ public static function setValues(&$params) { // custom_X => value or custom_X_VALUEID => value (for multiple values), VALUEID == -1, -2 etc for new insertions $fieldValues = []; foreach ($params as $n => $v) { - if ($customFieldInfo = CRM_Core_BAO_CustomField::getKeyID($n, TRUE)) { - $fieldID = (int ) $customFieldInfo[0]; - if (CRM_Utils_Type::escape($fieldID, 'Integer', FALSE) === NULL) { - throw new CRM_Core_Exception(ts('field ID needs to be of type Integer for index %1', - [1 => $fieldID] - )); - } - if (!array_key_exists($fieldID, $fieldValues)) { - $fieldValues[$fieldID] = []; - } + $customFieldInfo = CRM_Core_BAO_CustomField::getKeyID($n, TRUE); + if ($customFieldInfo[0]) { + $fieldId = (int) $customFieldInfo[0]; $id = -1; if ($customFieldInfo[1]) { - $id = (int ) $customFieldInfo[1]; + $id = (int) $customFieldInfo[1]; } - $fieldValues[$fieldID][] = [ + $fieldValues[$fieldId][] = [ 'value' => $v, 'id' => $id, ]; } } - $fieldIDList = implode(',', array_keys($fieldValues)); - - // format it so that we can just use create - $sql = " -SELECT cg.table_name as table_name , - cg.id as cg_id , - cg.is_multiple as is_multiple, - cg.extends as extends, - cf.column_name as column_name, - cf.id as cf_id , - cf.html_type as html_type , - cf.data_type as data_type , - cf.serialize as serialize -FROM civicrm_custom_group cg, - civicrm_custom_field cf -WHERE cf.custom_group_id = cg.id -AND cf.id IN ( $fieldIDList ) -"; - - $dao = CRM_Core_DAO::executeQuery($sql); $cvParams = []; - - while ($dao->fetch()) { - $dataType = $dao->data_type == 'Date' ? 'Timestamp' : $dao->data_type; - foreach ($fieldValues[$dao->cf_id] as $fieldValue) { + foreach ($fieldValues as $fieldId => $fieldVals) { + $fieldInfo = CRM_Core_BAO_CustomField::getField($fieldId); + if (!$fieldInfo) { + throw new CRM_Core_Exception("Custom field with id $fieldId not found."); + } + $dataType = $fieldInfo['data_type'] == 'Date' ? 'Timestamp' : $fieldInfo['data_type']; + foreach ($fieldVals as $fieldValue) { // Serialize array values - if (is_array($fieldValue['value']) && CRM_Core_BAO_CustomField::isSerialized($dao)) { + if (is_array($fieldValue['value']) && CRM_Core_BAO_CustomField::isSerialized($fieldInfo)) { $fieldValue['value'] = CRM_Utils_Array::implodePadded($fieldValue['value']); } // Format null values correctly @@ -657,7 +632,7 @@ public static function setValues(&$params) { throw new CRM_Core_Exception(ts('value: %1 is not of the right field data type: %2', [ 1 => $fieldValue['value'], - 2 => $dao->data_type, + 2 => $fieldInfo['data_type'], ] )); } @@ -666,13 +641,13 @@ public static function setValues(&$params) { 'entity_id' => $params['entityID'], 'value' => $fieldValue['value'], 'type' => $dataType, - 'custom_field_id' => $dao->cf_id, - 'custom_group_id' => $dao->cg_id, - 'table_name' => $dao->table_name, - 'column_name' => $dao->column_name, - 'is_multiple' => $dao->is_multiple, - 'serialize' => $dao->serialize, - 'extends' => $dao->extends, + 'custom_field_id' => $fieldInfo['id'], + 'custom_group_id' => $fieldInfo['custom_group']['id'], + 'table_name' => $fieldInfo['custom_group']['table_name'], + 'column_name' => $fieldInfo['column_name'], + 'is_multiple' => $fieldInfo['custom_group']['is_multiple'], + 'serialize' => $fieldInfo['serialize'], + 'extends' => $fieldInfo['custom_group']['extends'], ]; if (!empty($params['id'])) { @@ -683,18 +658,18 @@ public static function setValues(&$params) { $cvParam['file_id'] = $fieldValue['value']; } - if (!array_key_exists($dao->table_name, $cvParams)) { - $cvParams[$dao->table_name] = []; + if (!array_key_exists($cvParam['table_name'], $cvParams)) { + $cvParams[$cvParam['table_name']] = []; } - if (!array_key_exists($fieldValue['id'], $cvParams[$dao->table_name])) { - $cvParams[$dao->table_name][$fieldValue['id']] = []; + if (!array_key_exists($fieldValue['id'], $cvParams[$cvParam['table_name']])) { + $cvParams[$cvParam['table_name']][$fieldValue['id']] = []; } if ($fieldValue['id'] > 0) { $cvParam['id'] = $fieldValue['id']; } - $cvParams[$dao->table_name][$fieldValue['id']][] = $cvParam; + $cvParams[$cvParam['table_name']][$fieldValue['id']][] = $cvParam; } } diff --git a/www/modules/civicrm/CRM/Core/BAO/EntityTag.php b/www/modules/civicrm/CRM/Core/BAO/EntityTag.php index 97c94866d..dcff52645 100644 --- a/www/modules/civicrm/CRM/Core/BAO/EntityTag.php +++ b/www/modules/civicrm/CRM/Core/BAO/EntityTag.php @@ -15,7 +15,7 @@ * @package CRM * @copyright CiviCRM LLC https://civicrm.org/licensing */ -class CRM_Core_BAO_EntityTag extends CRM_Core_DAO_EntityTag { +class CRM_Core_BAO_EntityTag extends CRM_Core_DAO_EntityTag implements \Civi\Core\HookInterface { use CRM_Core_DynamicFKAccessTrait; /** @@ -457,7 +457,7 @@ public static function buildOptions($fieldName, $context = NULL, $props = []) { $options = []; foreach (self::buildOptions($fieldName) as $tableName => $label) { $bao = CRM_Core_DAO_AllCoreTables::getClassForTable($tableName); - $apiName = CRM_Core_DAO_AllCoreTables::getBriefName($bao); + $apiName = CRM_Core_DAO_AllCoreTables::getEntityNameForClass($bao); $options[$tableName] = $apiName; } } @@ -484,7 +484,7 @@ public static function preDeleteOtherEntity($event) { } // This is probably fairly mild in terms of helping performance - a case could be made to check if tags // exist before deleting (further down) as delete is a locking action. - $entity = CRM_Core_DAO_AllCoreTables::getBriefName(get_class($event->object)); + $entity = CRM_Core_DAO_AllCoreTables::getEntityNameForClass(get_class($event->object)); if ($entity && !isset(Civi::$statics[__CLASS__]['tagged_entities'][$entity])) { $tableName = CRM_Core_DAO_AllCoreTables::getTableForEntityName($entity); $used_for = CRM_Core_OptionGroup::values('tag_used_for'); diff --git a/www/modules/civicrm/CRM/Core/BAO/Job.php b/www/modules/civicrm/CRM/Core/BAO/Job.php index 7ea513273..203839714 100644 --- a/www/modules/civicrm/CRM/Core/BAO/Job.php +++ b/www/modules/civicrm/CRM/Core/BAO/Job.php @@ -126,6 +126,14 @@ public static function copy($id, $params = []) { */ public static function parseParameters(?string $parameters): array { $parameters = trim($parameters ?? ''); + if (!empty($parameters) && $parameters[0] === '{') { + try { + return json_decode($parameters, TRUE, 512, JSON_THROW_ON_ERROR); + } + catch (JsonException $e) { + throw new CRM_Core_Exception('Job parameters error: ' . $e->getMessage() . '. Parameters: ' . print_r($parameters, TRUE)); + } + } $result = ['version' => 3]; $lines = $parameters ? explode("\n", $parameters) : []; diff --git a/www/modules/civicrm/CRM/Core/BAO/LabelFormat.php b/www/modules/civicrm/CRM/Core/BAO/LabelFormat.php index bb8fa5a92..52939f5db 100644 --- a/www/modules/civicrm/CRM/Core/BAO/LabelFormat.php +++ b/www/modules/civicrm/CRM/Core/BAO/LabelFormat.php @@ -391,7 +391,7 @@ public static function getValue($field, &$values, $default = NULL) { $f = rtrim($f, '.'); return (float) (empty($f) ? '0' : $f); } - return CRM_Utils_Array::value($field, $values, $default); + return $values[$field] ?? $default; } return $default; } diff --git a/www/modules/civicrm/CRM/Core/BAO/Log.php b/www/modules/civicrm/CRM/Core/BAO/Log.php index e6e83aac4..d1868c791 100644 --- a/www/modules/civicrm/CRM/Core/BAO/Log.php +++ b/www/modules/civicrm/CRM/Core/BAO/Log.php @@ -158,31 +158,25 @@ public static function getContactLogCount($contactID) { * If logging is not enabled a return value of FALSE means to use the * basic change log view. * - * @return int|FALSE + * @return int|false * report id of Contact Logging Report (Summary) */ public static function useLoggingReport() { - if (!\Civi::settings()->get('logging')) { + $loggingSchema = new CRM_Logging_Schema(); + if (!$loggingSchema->isEnabled()) { return FALSE; } - - $loggingSchema = new CRM_Logging_Schema(); - - if ($loggingSchema->isEnabled()) { - $params = ['report_id' => 'logging/contact/summary']; - $instance = []; - CRM_Report_BAO_ReportInstance::retrieve($params, $instance); - - if (!empty($instance) && - (empty($instance['permission']) || - (!empty($instance['permission']) && CRM_Core_Permission::check($instance['permission'])) - ) - ) { - return $instance['id']; - } + try { + // Use civicrm_api4 wrapper as it will exception rather than fatal if civi-report disabled. + return civicrm_api4('ReportInstance', 'get', [ + 'where' => [['report_id', '=', 'logging/contact/summary']], + ])->first()['id'] ?? FALSE; + } + catch (CRM_Core_Exception $e) { + // Either CiviReport is disabled or the contact does not have permission to + // view the summary report. Return FALSE to use the basic log. + return FALSE; } - - return FALSE; } } diff --git a/www/modules/civicrm/CRM/Core/BAO/MailSettings.php b/www/modules/civicrm/CRM/Core/BAO/MailSettings.php index 53a719272..7ba2ebc4d 100644 --- a/www/modules/civicrm/CRM/Core/BAO/MailSettings.php +++ b/www/modules/civicrm/CRM/Core/BAO/MailSettings.php @@ -48,25 +48,30 @@ public static function setupStandardAccount($setupAction) { } /** - * Return the DAO object containing to the default row of + * Return the BAO object containing to the default row of * civicrm_mail_settings and cache it for further calls * - * @param bool $reset - * * @return CRM_Core_BAO_MailSettings * DAO with the default mail settings set */ - public static function defaultDAO($reset = FALSE) { - static $mailSettings = []; + public static function defaultDAO(): self { $domainID = CRM_Core_Config::domainID(); - if (empty($mailSettings[$domainID]) || $reset) { + if (!isset(\Civi::$statics[__CLASS__][__FUNCTION__][$domainID])) { + \Civi::$statics[__CLASS__][__FUNCTION__][$domainID] = []; $dao = new self(); $dao->is_default = 1; $dao->domain_id = $domainID; $dao->find(TRUE); - $mailSettings[$domainID] = $dao; + \Civi::$statics[__CLASS__][__FUNCTION__][$domainID] = $dao; } - return $mailSettings[$domainID]; + return \Civi::$statics[__CLASS__][__FUNCTION__][$domainID]; + } + + /** + * Clear cached variables. + */ + public static function clearCache(): void { + unset(\Civi::$statics[__CLASS__]); } /** @@ -75,8 +80,8 @@ public static function defaultDAO($reset = FALSE) { * @return string * default domain */ - public static function defaultDomain() { - return self::defaultDAO()->domain; + public static function defaultDomain(): string { + return self::defaultDAO()->domain ?? ''; } /** @@ -125,11 +130,12 @@ public static function retrieve($params, &$defaults) { * * @param array $params * Reference array contains the values submitted by the form. - * + * @deprecated since 5.72 will be removed around 5.82 * * @return CRM_Core_DAO_MailSettings */ - public static function add(&$params) { + public static function add($params) { + CRM_Core_Error::deprecatedFunctionWarning('use apiv4'); $result = NULL; if (empty($params)) { return $result; @@ -158,22 +164,28 @@ public static function add(&$params) { * Takes an associative array and creates a mail settings object. * * @param array $params - * (reference ) an assoc array of name/value pairs. * - * @return CRM_Core_DAO_MailSettings|CRM_Core_Error + * @return CRM_Core_DAO_MailSettings + * @throws \CRM_Core_Exception */ - public static function create(&$params) { + public static function create(array $params): CRM_Core_DAO_MailSettings { + if (empty($params['id'])) { + $params['is_ssl'] ??= FALSE; + $params['is_default'] ??= FALSE; + } + $transaction = new CRM_Core_Transaction(); - $mailSettings = self::add($params); - if (is_a($mailSettings, 'CRM_Core_Error')) { - $mailSettings->rollback(); - return $mailSettings; + if (!empty($params['is_default'])) { + $query = 'UPDATE civicrm_mail_settings SET is_default = 0 WHERE domain_id = %1'; + $queryParams = [1 => [CRM_Core_Config::domainID(), 'Integer']]; + CRM_Core_DAO::executeQuery($query, $queryParams); } + $result = self::writeRecord($params); $transaction->commit(); - CRM_Core_BAO_MailSettings::defaultDAO(TRUE); - return $mailSettings; + CRM_Core_BAO_MailSettings::clearCache(); + return $result; } /** diff --git a/www/modules/civicrm/CRM/Core/BAO/Mapping.php b/www/modules/civicrm/CRM/Core/BAO/Mapping.php index 804e3b436..51fecde7e 100644 --- a/www/modules/civicrm/CRM/Core/BAO/Mapping.php +++ b/www/modules/civicrm/CRM/Core/BAO/Mapping.php @@ -532,15 +532,16 @@ public function getRelationTypeCustomGroupData($relationshipTypeId) { } /** - * Function returns all Custom group Names. + * Unused function. + * @deprecated since 5.71 will be removed around 5.85. * - * @param int $customfieldId - * Related file id. + * @param string $customfieldId * * @return null|string * $customGroupName all custom group names */ public static function getCustomGroupName($customfieldId) { + CRM_Core_Error::deprecatedFunctionWarning('CRM_Core_BAO_CustomField::getField'); if ($customFieldId = CRM_Core_BAO_CustomField::getKeyID($customfieldId)) { $customGroupId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', $customFieldId, 'custom_group_id'); $customGroupName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $customGroupId, 'title'); diff --git a/www/modules/civicrm/CRM/Core/BAO/MessageTemplate.php b/www/modules/civicrm/CRM/Core/BAO/MessageTemplate.php index e9342273a..23b5c094d 100644 --- a/www/modules/civicrm/CRM/Core/BAO/MessageTemplate.php +++ b/www/modules/civicrm/CRM/Core/BAO/MessageTemplate.php @@ -193,7 +193,7 @@ public static function getMessageTemplates($all = TRUE, $isSMS = FALSE) { ->addWhere('is_sms', '=', $isSMS); if (!$all) { - $messageTemplates->addWhere('workflow_id', 'IS NULL'); + $messageTemplates->addWhere('workflow_name', 'IS NULL'); } $msgTpls = array_column((array) $messageTemplates->execute(), 'msg_title', 'id'); diff --git a/www/modules/civicrm/CRM/Core/BAO/Navigation.php b/www/modules/civicrm/CRM/Core/BAO/Navigation.php index 69774eefd..1c9517dcc 100644 --- a/www/modules/civicrm/CRM/Core/BAO/Navigation.php +++ b/www/modules/civicrm/CRM/Core/BAO/Navigation.php @@ -77,7 +77,7 @@ public static function add(&$params) { if (empty($params['id'])) { $params['is_active'] ??= FALSE; $params['has_separator'] ??= FALSE; - $params['domain_id'] = CRM_Utils_Array::value('domain_id', $params, CRM_Core_Config::domainID()); + $params['domain_id'] = $params['domain_id'] ?? CRM_Core_Config::domainID(); } if (!isset($params['id']) || @@ -903,6 +903,16 @@ public static function buildHomeMenu(&$menu) { ], ]; } + else { + $item['child'][] = [ + 'attributes' => [ + 'label' => ts('Change Password'), + 'name' => 'Change Password', + 'url' => 'civicrm/admin/user/password', + 'weight' => 2, + ], + ]; + } $item['child'][] = [ 'attributes' => [ 'label' => ts('Log out'), diff --git a/www/modules/civicrm/CRM/Core/BAO/OptionValue.php b/www/modules/civicrm/CRM/Core/BAO/OptionValue.php index 1fb48d51b..453cca2fc 100644 --- a/www/modules/civicrm/CRM/Core/BAO/OptionValue.php +++ b/www/modules/civicrm/CRM/Core/BAO/OptionValue.php @@ -179,6 +179,12 @@ public static function add(&$params) { $optionValue->find(TRUE); self::updateOptionDefaults($params['option_group_id'], $optionValue->id, $optionValue, $groupName); } + if (!empty($params['custom']) && + is_array($params['custom']) + ) { + CRM_Core_BAO_CustomValueTable::store($params['custom'], 'civicrm_option_value', $optionValue->id, $op); + } + Civi::cache('metadata')->flush(); CRM_Core_PseudoConstant::flush(); diff --git a/www/modules/civicrm/CRM/Core/BAO/PaperSize.php b/www/modules/civicrm/CRM/Core/BAO/PaperSize.php index 9af168098..ea106bc1e 100644 --- a/www/modules/civicrm/CRM/Core/BAO/PaperSize.php +++ b/www/modules/civicrm/CRM/Core/BAO/PaperSize.php @@ -203,7 +203,7 @@ public static function getValue($field, &$values, $default = NULL) { $f = rtrim($f, '.'); return (float) (empty($f) ? '0' : $f); } - return CRM_Utils_Array::value($field, $values, $default); + return $values[$field] ?? $default; } return $default; } diff --git a/www/modules/civicrm/CRM/Core/BAO/PdfFormat.php b/www/modules/civicrm/CRM/Core/BAO/PdfFormat.php index 385e8f862..d4f1df7a1 100644 --- a/www/modules/civicrm/CRM/Core/BAO/PdfFormat.php +++ b/www/modules/civicrm/CRM/Core/BAO/PdfFormat.php @@ -256,7 +256,7 @@ public static function getValue($field, $values, $default = NULL) { $f = rtrim($f, '.'); return (float) (empty($f) ? '0' : $f); } - return CRM_Utils_Array::value($field, $values, $default); + return $values[$field] ?? $default; } return $default; } diff --git a/www/modules/civicrm/CRM/Core/BAO/Phone.php b/www/modules/civicrm/CRM/Core/BAO/Phone.php index 1c1b00558..f34f4c80d 100644 --- a/www/modules/civicrm/CRM/Core/BAO/Phone.php +++ b/www/modules/civicrm/CRM/Core/BAO/Phone.php @@ -240,4 +240,37 @@ public static function del($id) { return (bool) self::deleteRecord(['id' => $id]); } + /** + * Customize search criteria for SMS autocompletes + * + * @param \Civi\Core\Event\GenericHookEvent $e + */ + public static function on_civi_search_autocompleteDefault(\Civi\Core\Event\GenericHookEvent $e) { + $formName = $e->formName ?? ''; + if (!str_contains($formName, '_Form_Task_SMS') || !is_array($e->savedSearch) || $e->savedSearch['api_entity'] !== 'Phone') { + return; + } + $e->savedSearch['api_params'] = [ + 'version' => 4, + 'select' => [ + 'id', + 'phone', + 'phone_numeric', + 'phone_type_id:label', + 'location_type_id:label', + 'contact_id.sort_name', + ], + 'orderBy' => [], + 'where' => [ + ['phone_type_id:name', '=', 'Mobile'], + ['contact_id.is_deleted', '=', FALSE], + ['contact_id.is_deceased', '=', FALSE], + ['contact_id.do_not_sms', '=', FALSE], + ], + 'groupBy' => [], + 'join' => [], + 'having' => [], + ]; + } + } diff --git a/www/modules/civicrm/CRM/Core/BAO/PrevNextCache.php b/www/modules/civicrm/CRM/Core/BAO/PrevNextCache.php index 1b5ee2f3d..4b75e8891 100644 --- a/www/modules/civicrm/CRM/Core/BAO/PrevNextCache.php +++ b/www/modules/civicrm/CRM/Core/BAO/PrevNextCache.php @@ -23,18 +23,26 @@ class CRM_Core_BAO_PrevNextCache extends CRM_Core_DAO_PrevNextCache { /** * Get the previous and next keys. * + * @internal as of Feb 2014 no universe usages other than defunct CiviHR + * code found. + * * @param string $cacheKey * @param int $id1 * @param int $id2 - * @param int $mergeId - * @param string $join - * @param string $where + * @param null $mergeId + * @param null $ignore + * @param null $ignore_more * @param bool $flip * * @return array + * @throws \CRM_Core_Exception + * @throws \Civi\Core\Exception\DBQueryException */ - public static function getPositions($cacheKey, $id1, $id2, &$mergeId = NULL, $join = NULL, $where = NULL, $flip = FALSE) { + public static function getPositions($cacheKey, $id1, $id2, &$mergeId = NULL, $ignore = NULL, $ignore_more = NULL, $flip = FALSE) { + $join = CRM_Dedupe_Merger::getJoinOnDedupeTable(); + $where = "de.id IS NULL"; if ($flip) { + CRM_Core_Error::deprecatedFunctionWarning('handle this outside the function'); [$id1, $id2] = [$id2, $id1]; } @@ -385,8 +393,68 @@ public static function refillCache($rgid, $gid, $criteria, $checkPermissions, $s } if (!empty($foundDupes)) { - CRM_Dedupe_Finder::parseAndStoreDupePairs($foundDupes, $cacheKeyString); + self::parseAndStoreDupePairs($foundDupes, $cacheKeyString); + } + } + + /** + * Parse duplicate pairs into a standardised array and store in the prev_next_cache. + * + * @param array $foundDupes + * @param string $cacheKeyString + * + * @return array + * Dupe pairs with the keys + * -srcID + * -srcName + * -dstID + * -dstName + * -weight + * -canMerge + */ + private static function parseAndStoreDupePairs($foundDupes, $cacheKeyString) { + $cids = []; + foreach ($foundDupes as $dupe) { + $cids[$dupe[0]] = 1; + $cids[$dupe[1]] = 1; + } + $cidString = implode(', ', array_keys($cids)); + + $dao = CRM_Core_DAO::executeQuery("SELECT id, display_name FROM civicrm_contact WHERE id IN ($cidString) ORDER BY sort_name"); + $displayNames = []; + while ($dao->fetch()) { + $displayNames[$dao->id] = $dao->display_name; + } + + $userId = CRM_Core_Session::getLoggedInContactID(); + foreach ($foundDupes as $dupes) { + $srcID = $dupes[1]; + $dstID = $dupes[0]; + // The logged in user should never be the src (ie. the contact to be removed). + if ($srcID == $userId) { + $srcID = $dstID; + $dstID = $userId; + } + + $mainContacts[] = $row = [ + 'dstID' => (int) $dstID, + 'dstName' => $displayNames[$dstID], + 'srcID' => (int) $srcID, + 'srcName' => $displayNames[$srcID], + 'weight' => $dupes[2], + 'canMerge' => TRUE, + ]; + + CRM_Core_DAO::executeQuery("INSERT INTO civicrm_prevnext_cache (entity_table, entity_id1, entity_id2, cacheKey, data) VALUES + ('civicrm_contact', %1, %2, %3, %4)", [ + 1 => [$dstID, 'Integer'], + 2 => [$srcID, 'Integer'], + 3 => [$cacheKeyString, 'String'], + 4 => [serialize($row), 'String'], + ] + ); } + return $mainContacts; } /** diff --git a/www/modules/civicrm/CRM/Core/BAO/Query.php b/www/modules/civicrm/CRM/Core/BAO/Query.php index 843820624..ae203fc1a 100644 --- a/www/modules/civicrm/CRM/Core/BAO/Query.php +++ b/www/modules/civicrm/CRM/Core/BAO/Query.php @@ -24,6 +24,12 @@ public static function addCustomFormFields(&$form, $extends) { $groupDetails = CRM_Core_BAO_CustomGroup::getGroupDetail(NULL, TRUE, $extends); if ($groupDetails) { foreach ($groupDetails as $group) { + if (empty($group['fields'])) { + // if there are no searchable fields in the custom group remove it + // from the details to avoid empty accordians per + // https://lab.civicrm.org/dev/core/-/issues/5112 + unset($groupDetails[$group['id']]); + } foreach ($group['fields'] as $field) { $fieldId = $field['id']; $elementName = 'custom_' . $fieldId; diff --git a/www/modules/civicrm/CRM/Core/BAO/RecurringEntity.php b/www/modules/civicrm/CRM/Core/BAO/RecurringEntity.php index cc1d7c1f8..636d75506 100644 --- a/www/modules/civicrm/CRM/Core/BAO/RecurringEntity.php +++ b/www/modules/civicrm/CRM/Core/BAO/RecurringEntity.php @@ -561,7 +561,7 @@ public static function copyCreateEntity($entityTable, $fromCriteria, $newParams, CRM_Core_BAO_RecurringEntity::quickAdd($object->id, $newObject->id, $entityTable); } - CRM_Utils_Hook::copy(CRM_Core_DAO_AllCoreTables::getBriefName($daoName), $newObject); + CRM_Utils_Hook::copy(CRM_Core_DAO_AllCoreTables::getEntityNameForClass($daoName), $newObject); return $newObject; } diff --git a/www/modules/civicrm/CRM/Core/BAO/SchemaHandler.php b/www/modules/civicrm/CRM/Core/BAO/SchemaHandler.php index f8b3d9d54..80a2255d9 100644 --- a/www/modules/civicrm/CRM/Core/BAO/SchemaHandler.php +++ b/www/modules/civicrm/CRM/Core/BAO/SchemaHandler.php @@ -39,6 +39,8 @@ */ class CRM_Core_BAO_SchemaHandler { + const DEFAULT_COLLATION = 'utf8mb4_unicode_ci'; + /** * Create a CiviCRM-table * @@ -785,7 +787,7 @@ public static function getIndexName($tableName, $columnName) { * * @return bool */ - public static function migrateUtf8mb4($revert = FALSE, $patterns = ['civicrm\_%'], $databaseList = NULL) { + public static function migrateUtf8mb4($revert = FALSE, $patterns = [], $databaseList = NULL) { $newCharSet = $revert ? 'utf8' : 'utf8mb4'; $newCollation = $revert ? 'utf8_unicode_ci' : 'utf8mb4_unicode_ci'; $newBinaryCollation = $revert ? 'utf8_bin' : 'utf8mb4_bin'; @@ -796,6 +798,8 @@ public static function migrateUtf8mb4($revert = FALSE, $patterns = ['civicrm\_%' $tableNameLikePatterns = []; $logTableNameLikePatterns = []; + $patterns = $patterns ?: CRM_Core_DAO::getTableNames(); + foreach ($patterns as $pattern) { $pattern = CRM_Utils_Type::escape($pattern, 'String'); $tableNameLikePatterns[] = "Name LIKE '{$pattern}'"; @@ -897,7 +901,7 @@ public static function getInUseCollation(): string { if (!isset(\Civi::$statics[__CLASS__][__FUNCTION__])) { $dao = CRM_Core_DAO::executeQuery('SHOW TABLE STATUS LIKE \'civicrm_contact\''); $dao->fetch(); - \Civi::$statics[__CLASS__][__FUNCTION__] = $dao->Collation; + \Civi::$statics[__CLASS__][__FUNCTION__] = $dao->Collation ?? self::DEFAULT_COLLATION; } return \Civi::$statics[__CLASS__][__FUNCTION__]; } diff --git a/www/modules/civicrm/CRM/Core/BAO/Tag.php b/www/modules/civicrm/CRM/Core/BAO/Tag.php index 5ea9ea897..253a6230d 100644 --- a/www/modules/civicrm/CRM/Core/BAO/Tag.php +++ b/www/modules/civicrm/CRM/Core/BAO/Tag.php @@ -56,7 +56,7 @@ public function getTree($usedFor = NULL, $excludeHidden = FALSE) { * @param bool $excludeHidden */ public function buildTree($usedFor = NULL, $excludeHidden = FALSE) { - $sql = "SELECT id, parent_id, name, description, is_selectable FROM civicrm_tag"; + $sql = "SELECT id, parent_id, name, label, description, is_selectable FROM civicrm_tag"; $whereClause = []; if ($usedFor) { @@ -81,6 +81,7 @@ public function buildTree($usedFor = NULL, $excludeHidden = FALSE) { $thisref['parent_id'] = $dao->parent_id; $thisref['name'] = $dao->name; + $thisref['label'] = $dao->label; $thisref['description'] = $dao->description; $thisref['is_selectable'] = $dao->is_selectable; if (!isset($thisref['children'])) { diff --git a/www/modules/civicrm/CRM/Core/BAO/UFField.php b/www/modules/civicrm/CRM/Core/BAO/UFField.php index 0212831e8..c802431e9 100644 --- a/www/modules/civicrm/CRM/Core/BAO/UFField.php +++ b/www/modules/civicrm/CRM/Core/BAO/UFField.php @@ -171,7 +171,7 @@ public static function duplicateField($params) { $ufField->field_type = $params['field_type'] ?? NULL; $ufField->field_name = $params['field_name'] ?? NULL; $ufField->website_type_id = $params['website_type_id'] ?? NULL; - if (is_null(CRM_Utils_Array::value('location_type_id', $params, ''))) { + if (array_key_exists('location_type_id', $params) && is_null($params['location_type_id'])) { // primary location type have NULL value in DB $ufField->whereAdd("location_type_id IS NULL"); } @@ -188,11 +188,11 @@ public static function duplicateField($params) { } /** - * Does profile consists of a multi-record custom field. + * Returns the id of the first multi-record custom group in this profile (if any). * * @param int $gId * - * @return bool + * @return int|false */ public static function checkMultiRecordFieldExists($gId) { $queryString = "SELECT f.field_name @@ -201,36 +201,18 @@ public static function checkMultiRecordFieldExists($gId) { AND g.id = %1 AND f.field_name LIKE 'custom%'"; $p = [1 => [$gId, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($queryString, $p); - $customFieldIds = []; - $isMultiRecordFieldPresent = FALSE; + while ($dao->fetch()) { - if ($customId = CRM_Core_BAO_CustomField::getKeyID($dao->field_name)) { - if (is_numeric($customId)) { - $customFieldIds[] = $customId; + $customId = CRM_Core_BAO_CustomField::getKeyID($dao->field_name); + if ($customId && is_numeric($customId)) { + $multiRecordGroupId = CRM_Core_BAO_CustomField::isMultiRecordField($customId); + if ($multiRecordGroupId) { + return $multiRecordGroupId; } } } - if (!empty($customFieldIds) && count($customFieldIds) == 1) { - $customFieldId = array_pop($customFieldIds); - $isMultiRecordFieldPresent = CRM_Core_BAO_CustomField::isMultiRecordField($customFieldId); - } - elseif (count($customFieldIds) > 1) { - $customFieldIds = implode(", ", $customFieldIds); - $queryString = " - SELECT cg.id as cgId - FROM civicrm_custom_group cg - INNER JOIN civicrm_custom_field cf - ON cg.id = cf.custom_group_id -WHERE cf.id IN (" . $customFieldIds . ") AND is_multiple = 1 LIMIT 0,1"; - - $dao = CRM_Core_DAO::executeQuery($queryString); - if ($dao->fetch()) { - $isMultiRecordFieldPresent = ($dao->cgId) ? $dao->cgId : FALSE; - } - } - - return $isMultiRecordFieldPresent; + return FALSE; } /** diff --git a/www/modules/civicrm/CRM/Core/BAO/UFGroup.php b/www/modules/civicrm/CRM/Core/BAO/UFGroup.php index 6451188dc..b1e2dba7e 100644 --- a/www/modules/civicrm/CRM/Core/BAO/UFGroup.php +++ b/www/modules/civicrm/CRM/Core/BAO/UFGroup.php @@ -457,7 +457,7 @@ protected static function formatUFField( if (isset($field->phone_type_id)) { $name .= "-{$field->phone_type_id}"; } - $fieldMetaData = CRM_Utils_Array::value($name, $importableFields, ($importableFields[$field->field_name] ?? [])); + $fieldMetaData = $importableFields[$name] ?? $importableFields[$field->field_name] ?? []; // No lie: this is bizarre; why do we need to mix so many UFGroup properties into UFFields? // I guess to make field self sufficient with all the required data and avoid additional calls @@ -469,15 +469,15 @@ protected static function formatUFField( 'groupHelpPre' => empty($group->help_pre) ? '' : $group->help_pre, 'groupHelpPost' => empty($group->help_post) ? '' : $group->help_post, 'title' => $title, - 'where' => CRM_Utils_Array::value('where', CRM_Utils_Array::value($field->field_name, $importableFields)), - 'attributes' => CRM_Core_DAO::makeAttribute(CRM_Utils_Array::value($field->field_name, $importableFields)), + 'where' => $importableFields[$field->field_name]['where'] ?? NULL, + 'attributes' => CRM_Core_DAO::makeAttribute($importableFields[$field->field_name] ?? NULL), 'is_required' => $field->is_required, 'is_view' => $field->is_view, 'help_pre' => $field->help_pre, 'help_post' => $field->help_post, 'visibility' => $field->visibility, 'in_selector' => $field->in_selector, - 'rule' => CRM_Utils_Array::value('rule', CRM_Utils_Array::value($field->field_name, $importableFields)), + 'rule' => $importableFields[$field->field_name]['rule'] ?? NULL, 'location_type_id' => $field->location_type_id ?? NULL, 'website_type_id' => $field->website_type_id ?? NULL, 'phone_type_id' => $field->phone_type_id ?? NULL, @@ -486,15 +486,9 @@ protected static function formatUFField( 'add_captcha' => $group->add_captcha ?? NULL, 'field_type' => $field->field_type, 'field_id' => $field->id, - 'pseudoconstant' => CRM_Utils_Array::value( - 'pseudoconstant', - CRM_Utils_Array::value($field->field_name, $importableFields) - ), + 'pseudoconstant' => $importableFields[$field->field_name]['pseudoconstant'] ?? NULL, // obsolete this when we remove the name / dbName discrepancy with gender/suffix/prefix - 'dbName' => CRM_Utils_Array::value( - 'dbName', - CRM_Utils_Array::value($field->field_name, $importableFields) - ), + 'dbName' => $importableFields[$field->field_name]['dbName'] ?? NULL, 'skipDisplay' => 0, 'data_type' => CRM_Utils_Type::getDataTypeFromFieldMetadata($fieldMetaData), 'bao' => $fieldMetaData['bao'] ?? NULL, @@ -716,30 +710,26 @@ public static function getLocationFields() { /** * @param $ctype - * @param int|bool $checkPermission - * @return mixed + * @param int|null $checkPermission + * @return array */ protected static function getCustomFields($ctype, $checkPermission = CRM_Core_Permission::VIEW) { // Only Edit and View is supported in ACL for custom field. - if ($checkPermission == CRM_Core_Permission::CREATE) { + if ($checkPermission && $checkPermission != CRM_Core_Permission::VIEW) { $checkPermission = CRM_Core_Permission::EDIT; } - // Make the cache user specific by adding the ID to the key. - $contactId = CRM_Core_Session::getLoggedInContactID(); - $cacheKey = 'uf_group_custom_fields_' . $ctype . '_' . $contactId . '_' . (int) $checkPermission; - if (!Civi::cache('metadata')->has($cacheKey)) { - $customFields = CRM_Core_BAO_CustomField::getFieldsForImport($ctype, FALSE, FALSE, FALSE, $checkPermission, TRUE); + $customFields = CRM_Core_BAO_CustomField::getFieldsForImport($ctype, FALSE, FALSE, FALSE, $checkPermission, TRUE); - // hack to add custom data for components - $components = ['Contribution', 'Participant', 'Membership', 'Activity', 'Case']; - foreach ($components as $value) { - $customFields = array_merge($customFields, CRM_Core_BAO_CustomField::getFieldsForImport($value)); - } - $addressCustomFields = CRM_Core_BAO_CustomField::getFieldsForImport('Address'); - $customFields = array_merge($customFields, $addressCustomFields); - Civi::cache('metadata')->set($cacheKey, [$customFields, $addressCustomFields]); + // Fixme: why this hardcoded list? If we truly want all fields we should use CRM_Core_BAO_CustomGroup::getAll(). + $components = ['Contribution', 'Participant', 'Membership', 'Activity', 'Case']; + foreach ($components as $value) { + $extraFields = CRM_Core_BAO_CustomField::getFieldsForImport($value, FALSE, FALSE, FALSE, $checkPermission); + $customFields = array_merge($customFields, $extraFields); } - return Civi::cache('metadata')->get($cacheKey); + $addressCustomFields = CRM_Core_BAO_CustomField::getFieldsForImport('Address', FALSE, FALSE, FALSE, $checkPermission); + $customFields = array_merge($customFields, $addressCustomFields); + // For some reason this function returns Address custom fields as a second item. + return [$customFields, $addressCustomFields]; } /** @@ -826,9 +816,7 @@ public static function getEditHTML( ); if ($reset || $doNotProcess) { // hack to make sure we do not process this form - $oldQFDefault = CRM_Utils_Array::value('_qf_default', - $_POST - ); + $oldQFDefault = $_POST['_qf_default'] ?? NULL; unset($_POST['_qf_default']); unset($_REQUEST['_qf_default']); if ($reset) { @@ -2149,9 +2137,7 @@ public static function buildProfile( } elseif (substr($fieldName, -11) == 'campaign_id') { if (CRM_Campaign_BAO_Campaign::isComponentEnabled()) { - $campaigns = CRM_Campaign_BAO_Campaign::getCampaigns(CRM_Utils_Array::value($contactId, - $form->_componentCampaigns - ), NULL, TRUE, FALSE); + $campaigns = CRM_Campaign_BAO_Campaign::getCampaigns($form->_componentCampaigns[$contactId] ?? NULL, NULL, TRUE, FALSE); $form->add('select', $name, $title, $campaigns, $required, [ @@ -2334,7 +2320,7 @@ public static function setProfileDefaults( // fixed for CRM-665 if (is_numeric($locTypeId)) { - if ($primaryLocationType || $locTypeId == CRM_Utils_Array::value('location_type_id', $value)) { + if ($primaryLocationType || $locTypeId == ($value['location_type_id'] ?? NULL)) { if (!empty($value[$fieldName])) { //to handle stateprovince and country if ($fieldName == 'state_province') { @@ -2646,41 +2632,21 @@ public static function commonSendMail($contactID, &$values) { return; } - $template = CRM_Core_Smarty::singleton(); - - $displayName = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', - $contactID, - 'display_name' - ); - - self::profileDisplay($values['id'], $values['values'], $template); $emailList = explode(',', $values['email']); - $contactLink = CRM_Utils_System::url('civicrm/contact/view', - "reset=1&cid=$contactID", - TRUE, NULL, FALSE, FALSE, TRUE - ); - //get the default domain email address. [$domainEmailName, $domainEmailAddress] = CRM_Core_BAO_Domain::getNameAndEmail(); - if (!$domainEmailAddress || $domainEmailAddress == 'info@EXAMPLE.ORG') { + if (!$domainEmailAddress || $domainEmailAddress === 'info@EXAMPLE.ORG') { $fixUrl = CRM_Utils_System::url('civicrm/admin/domain', 'action=update&reset=1'); CRM_Core_Error::statusBounce(ts('The site administrator needs to enter a valid \'FROM Email Address\' in Administer CiviCRM » Communications » FROM Email Addresses. The email address used may need to be a valid mail account with your email service provider.', [1 => $fixUrl])); } foreach ($emailList as $emailTo) { - // FIXME: take the below out of the foreach loop CRM_Core_BAO_MessageTemplate::sendTemplate( [ - 'groupName' => 'msg_tpl_workflow_uf', 'workflow' => 'uf_notify', - 'contactId' => $contactID, - 'tplParams' => [ - 'displayName' => $displayName, - 'currentDate' => date('r'), - 'contactLink' => $contactLink, - ], + 'modelProps' => ['contactID' => $contactID, 'profileID' => $values['id'], 'profileFields' => $values['values']], 'from' => "$domainEmailName <$domainEmailAddress>", 'toEmail' => $emailTo, ] @@ -2726,22 +2692,6 @@ public static function checkFieldsEmptyValues($gid, $cid, $params, $skipCheck = return NULL; } - /** - * Assign uf fields to template. - * - * @param int $gid - * Group id. - * @param array $values - * @param CRM_Core_Smarty $template - */ - public static function profileDisplay($gid, $values, $template) { - $groupTitle = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', $gid, 'title'); - $template->assign('grouptitle', $groupTitle); - if (count($values)) { - $template->assign('values', $values); - } - } - /** * Format fields for dupe Contact Matching. * @@ -3173,7 +3123,8 @@ public static function setComponentDefaults(&$fields, $componentId, $component, //FIX ME: We need to loop defaults, but once we move to custom_1_x convention this code can be simplified. foreach ($defaults as $customKey => $customValue) { - if ($customFieldDetails = CRM_Core_BAO_CustomField::getKeyID($customKey, TRUE)) { + $customFieldDetails = CRM_Core_BAO_CustomField::getKeyID($customKey, TRUE); + if ($customFieldDetails[0]) { if ($name == 'custom_' . $customFieldDetails[0]) { //hack to set default for checkbox @@ -3183,7 +3134,7 @@ public static function setComponentDefaults(&$fields, $componentId, $component, foreach ($formattedGroupTree as $tree) { if (!empty($tree['fields'][$customFieldDetails[0]])) { - if ('CheckBox' == CRM_Utils_Array::value('html_type', $tree['fields'][$customFieldDetails[0]])) { + if ('CheckBox' == ($tree['fields'][$customFieldDetails[0]]['html_type'] ?? NULL)) { $skipValue = TRUE; $defaults['field'][$componentId][$name] = $customValue; break; @@ -3543,8 +3494,8 @@ public static function reformatProfileFields(&$fields) { * @throws \CRM_Core_Exception */ public static function getFrontEndTitle(int $profileID) { - $profile = civicrm_api3('UFGroup', 'getsingle', ['id' => $profileID, 'return' => ['title', 'frontend_title']]); - return $profile['frontend_title'] ?? $profile['title']; + $profile = civicrm_api3('UFGroup', 'getsingle', ['id' => $profileID, 'return' => ['frontend_title']]); + return $profile['frontend_title']; } /** diff --git a/www/modules/civicrm/CRM/Core/BAO/UFMatch.php b/www/modules/civicrm/CRM/Core/BAO/UFMatch.php index 3d874a6d6..dca95233a 100644 --- a/www/modules/civicrm/CRM/Core/BAO/UFMatch.php +++ b/www/modules/civicrm/CRM/Core/BAO/UFMatch.php @@ -113,8 +113,8 @@ public static function synchronize(&$user, $update, $uf, $ctype, $isLogin = FALS // Are we processing logged in user. if ($loggedInUserUfID && $loggedInUserUfID != $ufID) { $userIds = self::getUFValues($loggedInUserUfID); - $ufID = CRM_Utils_Array::value('uf_id', $userIds, ''); - $userID = CRM_Utils_Array::value('contact_id', $userIds, ''); + $ufID = $userIds['uf_id'] ?? ''; + $userID = $userIds['contact_id'] ?? ''; } } diff --git a/www/modules/civicrm/CRM/Core/BAO/UserJob.php b/www/modules/civicrm/CRM/Core/BAO/UserJob.php index 3f7d3f6e0..28465f6b6 100644 --- a/www/modules/civicrm/CRM/Core/BAO/UserJob.php +++ b/www/modules/civicrm/CRM/Core/BAO/UserJob.php @@ -62,7 +62,7 @@ public static function hook_civicrm_queueStatus(CRM_Queue_Queue $queue, string $ if ($userJobId && $status === 'completed') { UserJob::update(FALSE) ->addWhere('id', '=', $userJobId) - ->setValues(['status_id' => 1]) + ->setValues(['status_id' => 1, 'end_date' => 'now']) ->execute(); } } diff --git a/www/modules/civicrm/CRM/Core/BAO/Website.php b/www/modules/civicrm/CRM/Core/BAO/Website.php index ab53574ed..1ab9aeb7a 100644 --- a/www/modules/civicrm/CRM/Core/BAO/Website.php +++ b/www/modules/civicrm/CRM/Core/BAO/Website.php @@ -18,7 +18,7 @@ /** * This class contain function for Website handling. */ -class CRM_Core_BAO_Website extends CRM_Core_DAO_Website { +class CRM_Core_BAO_Website extends CRM_Core_DAO_Website implements Civi\Core\HookInterface { use CRM_Contact_AccessTrait; /** diff --git a/www/modules/civicrm/CRM/Core/BAO/WordReplacement.php b/www/modules/civicrm/CRM/Core/BAO/WordReplacement.php index 2de2135ca..b9ecb5ef2 100644 --- a/www/modules/civicrm/CRM/Core/BAO/WordReplacement.php +++ b/www/modules/civicrm/CRM/Core/BAO/WordReplacement.php @@ -263,11 +263,8 @@ public static function rebuildWordReplacementTable() { * List of word replacements (enabled/disabled) for the given locale. */ public static function getLocaleCustomStrings($locale, $domainId = NULL) { - if ($domainId === NULL) { - $domainId = CRM_Core_Config::domainID(); - } - - return CRM_Utils_Array::value($locale, self::_getLocaleCustomStrings($domainId)); + $domainId ??= CRM_Core_Config::domainID(); + return self::_getLocaleCustomStrings($domainId)[$locale] ?? []; } /** diff --git a/www/modules/civicrm/CRM/Core/Block.php b/www/modules/civicrm/CRM/Core/Block.php index e33e92893..c7ce0cb0d 100644 --- a/www/modules/civicrm/CRM/Core/Block.php +++ b/www/modules/civicrm/CRM/Core/Block.php @@ -392,16 +392,6 @@ private static function setTemplateShortcutValues() { $values = []; - // Deprecated hook with typo. Please don't use this! - CRM_Utils_Hook::links('create.new.shorcuts', - NULL, - CRM_Core_DAO::$_nullObject, - $values - ); - if ($values) { - CRM_Core_Error::deprecatedWarning('hook_civicrm_links "create.new.shorcuts" deprecated in favor of "create.new.shortcuts"'); - } - foreach ($shortCuts as $key => $short) { $values[$key] = self::setShortCutValues($short); } @@ -409,7 +399,7 @@ private static function setTemplateShortcutValues() { // Hook that enables extensions to add user-defined links CRM_Utils_Hook::links('create.new.shortcuts', NULL, - CRM_Core_DAO::$_nullObject, + NULL, $values ); diff --git a/www/modules/civicrm/CRM/Core/ClassLoader.php b/www/modules/civicrm/CRM/Core/ClassLoader.php index b92512ad3..acfc35c6a 100644 --- a/www/modules/civicrm/CRM/Core/ClassLoader.php +++ b/www/modules/civicrm/CRM/Core/ClassLoader.php @@ -138,6 +138,10 @@ public function register($prepend = FALSE) { set_include_path($include_paths . PATH_SEPARATOR . get_include_path()); // @todo Why do we need to load this again? $this->requireComposerAutoload(); + + $mixinLib = dirname(__DIR__, 2) . '/mixin/lib'; + ($GLOBALS['_PathLoad'][0] ?? require "$mixinLib/pathload-0.php"); + require_once "$mixinLib/pathload.index.php"; } /** diff --git a/www/modules/civicrm/CRM/Core/CodeGen/GenerateData.php b/www/modules/civicrm/CRM/Core/CodeGen/GenerateData.php index 4289ed1cf..0dbc5c300 100644 --- a/www/modules/civicrm/CRM/Core/CodeGen/GenerateData.php +++ b/www/modules/civicrm/CRM/Core/CodeGen/GenerateData.php @@ -277,6 +277,10 @@ public function initID() { private $deceasedContactIds = []; + private $time; + + private $relTypes; + /********************************* * private methods * ******************************* @@ -1556,6 +1560,9 @@ private function addMembership() { * @return string */ public static function repairDate($date) { + if ($date === NULL) { + return ''; + } $dropArray = ['-' => '', ':' => '', ' ' => '']; return strtr($date, $dropArray); } diff --git a/www/modules/civicrm/CRM/Core/CodeGen/Main.php b/www/modules/civicrm/CRM/Core/CodeGen/Main.php index fe90d38f5..b3df7e092 100644 --- a/www/modules/civicrm/CRM/Core/CodeGen/Main.php +++ b/www/modules/civicrm/CRM/Core/CodeGen/Main.php @@ -145,7 +145,7 @@ public function getTasks() { $tasks = []; $tasks[] = new CRM_Core_CodeGen_Config($this); $tasks[] = new CRM_Core_CodeGen_Reflection($this); - $tasks[] = new CRM_Core_CodeGen_Schema($this); + $tasks[] = new CRM_Core_CodeGen_PhpSchema($this); foreach (array_keys($this->tables) as $name) { $tasks[] = new CRM_Core_CodeGen_DAO($this, $name); } diff --git a/www/modules/civicrm/CRM/Core/CodeGen/PhpSchema.php b/www/modules/civicrm/CRM/Core/CodeGen/PhpSchema.php new file mode 100644 index 000000000..a36028bd3 --- /dev/null +++ b/www/modules/civicrm/CRM/Core/CodeGen/PhpSchema.php @@ -0,0 +1,149 @@ +locales = $this->findLocales(); + } + + public function run() { + CRM_Core_CodeGen_Util_File::createDir($this->config->sqlCodePath); + + $put = function ($files) { + foreach ($files as $file => $content) { + if (substr($content, -1) !== "\n") { + $content .= "\n"; + } + file_put_contents($this->config->sqlCodePath . $file, $content); + } + }; + + echo "Generating sql file\n"; + $put($this->generateCreateSql()); + + echo "Generating sql drop tables file\n"; + $put($this->generateDropSql()); + + foreach ($this->locales as $locale) { + echo "Generating data files for $locale\n"; + $put($this->generateLocaleDataSql($locale)); + } + + // also create the archive tables + // $this->generateCreateSql('civicrm_archive.mysql' ); + // $this->generateDropSql('civicrm_archive_drop.mysql'); + + echo "Generating navigation file\n"; + $put($this->generateNavigation()); + + echo "Generating sample file\n"; + $put($this->generateSample()); + } + + public function generateCreateSql() { + return ['civicrm.mysql' => \Civi::schemaHelper()->generateUninstallSql() . \Civi::schemaHelper()->generateInstallSql()]; + } + + public function generateDropSql() { + return ['civicrm_drop.mysql' => \Civi::schemaHelper()->generateUninstallSql()]; + } + + public function generateNavigation() { + $template = new CRM_Core_CodeGen_Util_Template('sql'); + return ['civicrm_navigation.mysql' => $template->fetch('civicrm_navigation.tpl')]; + } + + /** + * @param string $locale + * Ex: en_US, fr_FR + * @return array + */ + public function generateLocaleDataSql($locale) { + $template = new CRM_Core_CodeGen_Util_Template('sql'); + CRM_Core_CodeGen_Util_MessageTemplates::assignSmartyVariables($template->getSmarty()); + global $tsLocale; + $oldTsLocale = $tsLocale; + + try { + + $tsLocale = $locale; + $template->assign('locale', $locale); + $template->assign('db_version', $this->config->db_version); + + $sections = [ + 'civicrm_country.tpl', + 'civicrm_state_province.tpl', + 'civicrm_currency.tpl', + 'civicrm_data.tpl', + 'civicrm_navigation.tpl', + 'civicrm_version_sql.tpl', + ]; + + $ext = ($locale !== 'en_US' ? ".$locale" : ''); + + return [ + "civicrm_data$ext.mysql" => $template->fetchConcat($sections), + "civicrm_acl$ext.mysql" => $template->fetch('civicrm_acl.tpl'), + ]; + } + finally { + $tsLocale = $oldTsLocale; + } + } + + /** + * @return array + * Array(string $fileName => string $fileContent). + * List of files + */ + public function generateSample() { + $template = new CRM_Core_CodeGen_Util_Template('sql'); + $sections = [ + 'civicrm_sample.tpl', + 'civicrm_acl.tpl', + ]; + return [ + 'civicrm_sample.mysql' => $template->fetchConcat($sections), + 'case_sample.mysql' => $template->fetch('case_sample.tpl'), + ]; + } + + /** + * @return array + */ + public function findLocales() { + require_once 'CRM/Core/Config.php'; + $config = CRM_Core_Config::singleton(FALSE); + $locales = []; + $localeDir = CRM_Core_I18n::getResourceDir(); + if (file_exists($localeDir)) { + $locales = preg_grep('/^[a-z][a-z]_[A-Z][A-Z]$/', scandir($localeDir)); + } + + $localesMask = getenv('CIVICRM_LOCALES'); + if (!empty($localesMask)) { + $mask = explode(',', $localesMask); + $locales = array_intersect($locales, $mask); + } + + if (!in_array('en_US', $locales)) { + array_unshift($locales, 'en_US'); + } + + return $locales; + } + +} diff --git a/www/modules/civicrm/CRM/Core/CodeGen/Schema.php b/www/modules/civicrm/CRM/Core/CodeGen/Schema.php index bc3e80462..f91d5f962 100644 --- a/www/modules/civicrm/CRM/Core/CodeGen/Schema.php +++ b/www/modules/civicrm/CRM/Core/CodeGen/Schema.php @@ -2,6 +2,11 @@ /** * Create SQL files to create and populate a new schema. + * + * @deprecated + * Replaced by CRM_Core_CodeGen_PhpSchema. + * Delete this after civicrm-core and civix drop their references. + * Maybe allow grace-period of a couple months. */ class CRM_Core_CodeGen_Schema extends CRM_Core_CodeGen_BaseTask { @@ -59,7 +64,7 @@ public function generateCreateSql() { $dropOrder = array_reverse(array_keys($this->tables)); $template->assign('dropOrder', $dropOrder); $template->assign('mysql', 'modern'); - + CRM_Core_CodeGen_Util_MessageTemplates::assignSmartyVariables($template->getSmarty()); return ['civicrm.mysql' => $template->fetch('schema.tpl')]; } @@ -83,7 +88,7 @@ public function generateNavigation() { */ public function generateLocaleDataSql($locale) { $template = new CRM_Core_CodeGen_Util_Template('sql'); - + CRM_Core_CodeGen_Util_MessageTemplates::assignSmartyVariables($template->getSmarty()); global $tsLocale; $oldTsLocale = $tsLocale; @@ -102,7 +107,7 @@ public function generateLocaleDataSql($locale) { 'civicrm_version_sql.tpl', ]; - $ext = ($locale != 'en_US' ? ".$locale" : ''); + $ext = ($locale !== 'en_US' ? ".$locale" : ''); return [ "civicrm_data$ext.mysql" => $template->fetchConcat($sections), diff --git a/www/modules/civicrm/CRM/Core/CodeGen/Specification.php b/www/modules/civicrm/CRM/Core/CodeGen/Specification.php index 0d7a12ca5..303efdc39 100644 --- a/www/modules/civicrm/CRM/Core/CodeGen/Specification.php +++ b/www/modules/civicrm/CRM/Core/CodeGen/Specification.php @@ -314,58 +314,7 @@ public function getTable($tableXML, &$database, &$tables) { public function getField(&$fieldXML, &$fields) { $name = trim((string ) $fieldXML->name); $field = ['name' => $name, 'localizable' => ((bool) $fieldXML->localizable) ? 1 : 0]; - $type = (string) $fieldXML->type; - switch ($type) { - case 'varchar': - case 'char': - $field['length'] = (int) $fieldXML->length; - $field['sqlType'] = "$type({$field['length']})"; - $field['crmType'] = 'CRM_Utils_Type::T_STRING'; - $field['size'] = $this->getSize($fieldXML); - break; - - case 'text': - $field['sqlType'] = $type; - $field['crmType'] = 'CRM_Utils_Type::T_' . strtoupper($type); - // CRM-13497 see fixme below - $field['rows'] = isset($fieldXML->html) ? $this->value('rows', $fieldXML->html) : NULL; - $field['cols'] = isset($fieldXML->html) ? $this->value('cols', $fieldXML->html) : NULL; - break; - - case 'datetime': - $field['sqlType'] = $type; - $field['crmType'] = 'CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME'; - break; - - case 'boolean': - // need this case since some versions of mysql do not have boolean as a valid column type and hence it - // is changed to tinyint. hopefully after 2 yrs this case can be removed. - $field['sqlType'] = 'tinyint'; - $field['crmType'] = 'CRM_Utils_Type::T_' . strtoupper($type); - break; - - case 'decimal': - $length = $fieldXML->length ? $fieldXML->length : '20,2'; - $field['sqlType'] = 'decimal(' . $length . ')'; - $field['crmType'] = $this->value('crmType', $fieldXML, 'CRM_Utils_Type::T_MONEY'); - $field['precision'] = $length . ','; - break; - - case 'float': - $field['sqlType'] = 'double'; - $field['crmType'] = 'CRM_Utils_Type::T_FLOAT'; - break; - - default: - $field['sqlType'] = $type; - if ($type === 'int unsigned' || $type === 'tinyint') { - $field['crmType'] = 'CRM_Utils_Type::T_INT'; - } - else { - $field['crmType'] = $this->value('crmType', $fieldXML, 'CRM_Utils_Type::T_' . strtoupper($type)); - } - break; - } + $field = array_merge($field, \CRM_Utils_Schema::getTypeAttributes($fieldXML)); $field['phpType'] = $this->getPhpType($fieldXML); $field['phpNullable'] = $this->getPhpNullable($fieldXML); @@ -375,32 +324,12 @@ public function getField(&$fieldXML, &$fields) { $field['comment'] = $this->value('comment', $fieldXML); $field['deprecated'] = $this->value('deprecated', $fieldXML, FALSE); $field['default'] = $this->value('default', $fieldXML); - $import = $this->value('import', $fieldXML) ? strtoupper($this->value('import', $fieldXML)) : 'FALSE'; - $export = $this->value('export', $fieldXML) ? strtoupper($this->value('export', $fieldXML)) : NULL; - if (!isset($fieldXML->usage)) { - $usage = [ - 'import' => $import, - 'export' => $export ?? $import, - ]; - } - else { - $usage = []; - foreach ($fieldXML->usage->children() as $usedFor => $isUsed) { - $usage[$usedFor] = strtoupper((string) $isUsed); - } - $import = $usage['import'] ?? $import; - } - // Ensure all keys are populated. Import is the historical de-facto default. - $field['usage'] = array_merge(array_fill_keys(['import', 'export', 'duplicate_matching'], $import), $usage); - // Usage for tokens has not historically been in the metadata so we can default to FALSE. - // historically hard-coded lists have been used. - $field['usage']['token'] ??= 'FALSE'; - $field['import'] = $field['usage']['import']; - $field['export'] = $export ?? $import; + + $field['usage'] = CRM_Utils_Schema::getFieldUsage($fieldXML); $field['rule'] = $this->value('rule', $fieldXML); $field['title'] = $this->value('title', $fieldXML); if (!$field['title']) { - $field['title'] = $this->composeTitle($name); + $field['title'] = CRM_Utils_Schema::composeTitle($name); } $field['headerPattern'] = $this->value('headerPattern', $fieldXML); $field['dataPattern'] = $this->value('dataPattern', $fieldXML); @@ -409,108 +338,13 @@ public function getField(&$fieldXML, &$fields) { $field['uniqueTitle'] = $this->value('uniqueTitle', $fieldXML); $field['serialize'] = $this->value('serialize', $fieldXML); $field['component'] = $this->value('component', $fieldXML); - $field['html'] = $this->value('html', $fieldXML); + $field['html'] = CRM_Utils_Schema::getFieldHtml($fieldXML); $field['contactType'] = $this->value('contactType', $fieldXML); - if (isset($fieldXML->permission)) { - $field['permission'] = trim($this->value('permission', $fieldXML)); - $field['permission'] = $field['permission'] ? array_filter(array_map('trim', explode(',', $field['permission']))) : []; - if (isset($fieldXML->permission->or)) { - $field['permission'][] = array_filter(array_map('trim', explode(',', $fieldXML->permission->or))); - } - } - if (!empty($field['html'])) { - $validOptions = [ - 'type', - 'formatType', - 'label', - 'controlField', - 'min', - 'max', - /* Fixme: prior to CRM-13497 these were in a flat structure - // CRM-13497 moved them to be nested within 'html' but there's no point - // making that change in the DAOs right now since we are in the process of - // moving to docrtine anyway. - // So translating from nested xml back to flat structure for now. - 'rows', - 'cols', - 'size', */ - ]; - $field['html'] = []; - foreach ($validOptions as $htmlOption) { - if (isset($fieldXML->html->$htmlOption) && $fieldXML->html->$htmlOption !== '') { - $field['html'][$htmlOption] = $this->value($htmlOption, $fieldXML->html); - } - } - if (isset($fieldXML->html->filter)) { - $field['html']['filter'] = (array) $fieldXML->html->filter; - } - } - - // in multilingual context popup, we need extra information to create appropriate widget - if ($fieldXML->localizable) { - if (isset($fieldXML->html)) { - $field['widget'] = (array) $fieldXML->html; - } - else { - // default - $field['widget'] = ['type' => 'Text']; - } - if (isset($fieldXML->required)) { - $field['widget']['required'] = $this->value('required', $fieldXML); - } - } - if (isset($fieldXML->localize_context)) { - $field['localize_context'] = $fieldXML->localize_context; - } + $field['permission'] = CRM_Utils_Schema::getFieldPermission($fieldXML); + $field['widget'] = CRM_Utils_Schema::getFieldWidget($fieldXML); + $field['localize_context'] = $this->value('localize_context', $fieldXML); $field['add'] = $this->value('add', $fieldXML); - $field['pseudoconstant'] = $this->value('pseudoconstant', $fieldXML); - if (!empty($field['pseudoconstant'])) { - //ok this is a bit long-winded but it gets there & is consistent with above approach - $field['pseudoconstant'] = []; - $validOptions = [ - // Fields can specify EITHER optionGroupName OR table, not both - // (since declaring optionGroupName means we are using the civicrm_option_value table) - 'optionGroupName', - 'table', - // If table is specified, keyColumn and labelColumn are also required - 'keyColumn', - 'labelColumn', - // Non-translated machine name for programmatic lookup. Defaults to 'name' if that column exists - 'nameColumn', - // Column to fetch in "abbreviate" context - 'abbrColumn', - // Supported by APIv4 suffixes - 'colorColumn', - 'iconColumn', - // Where clause snippet (will be joined to the rest of the query with AND operator) - 'condition', - // callback function incase of static arrays - 'callback', - // Path to options edit form - 'optionEditPath', - // Should options for this field be prefetched (for presenting on forms). - // The default is TRUE, but adding FALSE helps when there could be many options - 'prefetch', - ]; - foreach ($validOptions as $pseudoOption) { - if (!empty($fieldXML->pseudoconstant->$pseudoOption)) { - $field['pseudoconstant'][$pseudoOption] = $this->value($pseudoOption, $fieldXML->pseudoconstant); - } - } - if (!isset($field['pseudoconstant']['optionEditPath']) && !empty($field['pseudoconstant']['optionGroupName'])) { - $field['pseudoconstant']['optionEditPath'] = 'civicrm/admin/options/' . $field['pseudoconstant']['optionGroupName']; - } - // Set suffixes if explicitly declared - if (!empty($fieldXML->pseudoconstant->suffixes)) { - $field['pseudoconstant']['suffixes'] = explode(',', $this->value('suffixes', $fieldXML->pseudoconstant)); - } - // For now, fields that have option lists that are not in the db can simply - // declare an empty pseudoconstant tag and we'll add this placeholder. - // That field's BAO::buildOptions fn will need to be responsible for generating the option list - if (empty($field['pseudoconstant'])) { - $field['pseudoconstant'] = 'not in database'; - } - } + $field['pseudoconstant'] = CRM_Utils_Schema::getFieldPseudoconstant($fieldXML); $fields[$name] = &$field; } @@ -558,31 +392,6 @@ private function getPhpNullable($fieldXML) { return !$required; } - /** - * @param string $name - * - * @return string - */ - public function composeTitle($name) { - $substitutions = [ - 'is_active' => 'Enabled', - ]; - if (isset($substitutions[$name])) { - return $substitutions[$name]; - } - $names = explode('_', strtolower($name)); - $allCaps = ['im', 'id']; - foreach ($names as $i => $str) { - if (in_array($str, $allCaps, TRUE)) { - $names[$i] = strtoupper($str); - } - else { - $names[$i] = ucfirst(trim($str)); - } - } - return trim(implode(' ', $names)); - } - /** * @param object $primaryXML * @param array $fields @@ -808,36 +617,4 @@ protected function append(&$str, $delim, $name) { } } - /** - * Sets the size property of a textfield. - * - * @param string $fieldXML - * - * @return null|string - */ - protected function getSize($fieldXML) { - // Extract from tag if supplied - if (!empty($fieldXML->html) && $this->value('size', $fieldXML->html)) { - return $this->value('size', $fieldXML->html); - } - // Infer from tag if was not explicitly set or was invalid - // This map is slightly different from CRM_Core_Form_Renderer::$_sizeMapper - // Because we usually want fields to render as smaller than their maxlength - $sizes = [ - 2 => 'TWO', - 4 => 'FOUR', - 6 => 'SIX', - 8 => 'EIGHT', - 16 => 'TWELVE', - 32 => 'MEDIUM', - 64 => 'BIG', - ]; - foreach ($sizes as $length => $name) { - if ($fieldXML->length <= $length) { - return "CRM_Utils_Type::$name"; - } - } - return 'CRM_Utils_Type::HUGE'; - } - } diff --git a/www/modules/civicrm/CRM/Core/CodeGen/Util/MessageTemplates.php b/www/modules/civicrm/CRM/Core/CodeGen/Util/MessageTemplates.php index 2a2dfa46b..e47546e16 100644 --- a/www/modules/civicrm/CRM/Core/CodeGen/Util/MessageTemplates.php +++ b/www/modules/civicrm/CRM/Core/CodeGen/Util/MessageTemplates.php @@ -255,9 +255,18 @@ public static function assignSmartyVariables($smarty): void { */ protected static function getDirectory($smarty) { if (method_exists($smarty, 'getTemplateDir')) { - return $smarty->getTemplateDir() . '/message_templates/'; + $directories = $smarty->getTemplateDir(); } - return $smarty->template_dir . '/message_templates/'; + else { + $directories = (array) $smarty->template_dir; + } + foreach ($directories as $directory) { + if (file_exists($directory . '/message_templates/')) { + return $directory . '/message_templates/'; + } + } + + return $directory . '/message_templates/'; } } diff --git a/www/modules/civicrm/CRM/Core/CodeGen/Util/Smarty.php b/www/modules/civicrm/CRM/Core/CodeGen/Util/Smarty.php index a1eb1f29c..2f3300cfc 100644 --- a/www/modules/civicrm/CRM/Core/CodeGen/Util/Smarty.php +++ b/www/modules/civicrm/CRM/Core/CodeGen/Util/Smarty.php @@ -43,25 +43,43 @@ public function getCompileDir() { * Create a Smarty instance. * * @return \Smarty + * @throws \SmartyException */ - public function createSmarty() { - $base = dirname(dirname(dirname(dirname(__DIR__)))); + public function createSmarty(): Smarty { + $base = dirname(__DIR__, 4); $pkgs = file_exists(dirname($base) . "/civicrm-packages") ? dirname($base) . "/civicrm-packages" : "$base/packages"; - require_once 'Smarty/Smarty.class.php'; + if (!defined('CIVICRM_SMARTY_AUTOLOAD_PATH')) { + define('CIVICRM_SMARTY_AUTOLOAD_PATH', $pkgs . '/smarty4/vendor/autoload.php'); + } + // FIXME: If it's already set, and it doesn't match, then that's a problem. + + require_once $pkgs . '/smarty4/vendor/autoload.php'; $smarty = new Smarty(); - $smarty->template_dir = "$base/xml/templates"; - $smarty->plugins_dir = ["$pkgs/Smarty/plugins", "$base/CRM/Core/Smarty/plugins"]; - $smarty->compile_dir = $this->getCompileDir(); - $smarty->clear_all_cache(); + $smarty->setTemplateDir("$base/xml/templates"); + $pluginsDirectory = $smarty->getPluginsDir(); + $pluginsDirectory[] = "$base/CRM/Core/Smarty/plugins"; + // Doesn't seem to work very well.... since I still need require_once below + $smarty->setPluginsDir($pluginsDirectory); + $smarty->setCompileDir($this->getCompileDir()); + $smarty->clearAllCache(); // CRM-5308 / CRM-3507 - we need {localize} to work in the templates - require_once 'CRM/Core/Smarty/plugins/block.localize.php'; - $smarty->register_block('localize', 'smarty_block_localize'); - $smarty->assign('gencodeXmlDir', dirname(dirname(dirname(dirname(__DIR__)))) . '/xml'); - require_once 'CRM/Core/CodeGen/Util/MessageTemplates.php'; - CRM_Core_CodeGen_Util_MessageTemplates::assignSmartyVariables($smarty); + $smarty->registerPlugin('block', 'localize', 'smarty_block_localize'); + + // Use our special replace rather than Smarty's to avoid conflicts while we + // transition from Smarty2. + require_once 'CRM/Core/Smarty/plugins/modifier.crmEscapeSingleQuotes.php'; + $smarty->registerPlugin('modifier', 'crmEscapeSingleQuotes', 'smarty_modifier_crmEscapeSingleQuotes'); + + require_once 'CRM/Core/Smarty/plugins/modifier.crmCountCharacters.php'; + $smarty->registerPlugin('modifier', 'crmCountCharacters', 'smarty_modifier_crmCountCharacters'); + + $smarty->registerPlugin('modifier', 'json_encode', 'json_encode'); + $smarty->registerPlugin('modifier', 'count', 'count'); + $smarty->registerPlugin('modifier', 'implode', 'implode'); + return $smarty; } diff --git a/www/modules/civicrm/CRM/Core/CodeGen/Util/Template.php b/www/modules/civicrm/CRM/Core/CodeGen/Util/Template.php index 6b59f98ec..67d00edd1 100644 --- a/www/modules/civicrm/CRM/Core/CodeGen/Util/Template.php +++ b/www/modules/civicrm/CRM/Core/CodeGen/Util/Template.php @@ -7,6 +7,11 @@ class CRM_Core_CodeGen_Util_Template { protected $filetype; protected $smarty; + + public function getSmarty(): Smarty { + return $this->smarty; + } + protected $beautifier; /** diff --git a/www/modules/civicrm/CRM/Core/Component/Info.php b/www/modules/civicrm/CRM/Core/Component/Info.php index 328f62d28..a3a502fd8 100644 --- a/www/modules/civicrm/CRM/Core/Component/Info.php +++ b/www/modules/civicrm/CRM/Core/Component/Info.php @@ -140,19 +140,11 @@ public function getAnonymousPermissionWarnings() { } /** - * Provides permissions that are used by component. - * Needs to be implemented in component's information - * class. - * - * NOTE: if using conditionally permission return, - * implementation of $getAllUnconditionally is required. + * Defines permissions that are used by component. * - * @param bool $getAllUnconditionally - * - * @return array|null - * collection of permissions, null if none + * @return array */ - abstract public function getPermissions($getAllUnconditionally = FALSE); + abstract public function getPermissions(); /** * Determine how many other records refer to a given record. diff --git a/www/modules/civicrm/CRM/Core/Config.php b/www/modules/civicrm/CRM/Core/Config.php index ce7043d2a..486820f5e 100644 --- a/www/modules/civicrm/CRM/Core/Config.php +++ b/www/modules/civicrm/CRM/Core/Config.php @@ -91,7 +91,14 @@ public static function &singleton($loadFromDB = TRUE, $force = FALSE) { // Standalone's session cannot be initialized until CiviCRM is booted, // since it is defined in an extension, and we need the session // initialized before calling applyLocale. - \CRM_Core_Session::singleton()->initialize(); + $sess = \CRM_Core_Session::singleton(); + $sess->initialize(); + if ($sess->getLoggedInContactID()) { + // Apply user's timezone. + if (is_callable([self::$_singleton->userSystem, 'setMySQLTimeZone'])) { + self::$_singleton->userSystem->setMySQLTimeZone(); + } + } } \CRM_Core_BAO_ConfigSetting::applyLocale(\Civi::settings($domain->id), $domain->locales); @@ -274,13 +281,16 @@ public static function environment($env = NULL, $reset = FALSE) { * * @param bool $sessionReset */ - public function cleanupCaches($sessionReset = TRUE) { + public function cleanupCaches($sessionReset = FALSE) { // cleanup templates_c directory $this->cleanup(1, FALSE); UserJob::delete(FALSE)->addWhere('expires_date', '<', 'now')->execute(); // clear all caches self::clearDBCache(); - Civi::cache('session')->clear(); + // Avoid clearing QuickForm sessions unless explicitly requested + if ($sessionReset) { + Civi::cache('session')->clear(); + } Civi::cache('metadata')->clear(); CRM_Core_DAO_AllCoreTables::flush(); CRM_Utils_System::flushCache(); @@ -342,7 +352,8 @@ public static function clearDBCache(): void { $queries = [ 'TRUNCATE TABLE civicrm_acl_cache', 'TRUNCATE TABLE civicrm_acl_contact_cache', - 'TRUNCATE TABLE civicrm_cache', + // Do not truncate, reduce risks of losing a quickform session + 'DELETE FROM civicrm_cache WHERE group_name NOT LIKE "CiviCRM%Session"', 'TRUNCATE TABLE civicrm_prevnext_cache', 'UPDATE civicrm_group SET cache_date = NULL', 'TRUNCATE TABLE civicrm_group_contact_cache', diff --git a/www/modules/civicrm/CRM/Core/Config/MagicMerge.php b/www/modules/civicrm/CRM/Core/Config/MagicMerge.php index 3e5f3b197..49b745e91 100644 --- a/www/modules/civicrm/CRM/Core/Config/MagicMerge.php +++ b/www/modules/civicrm/CRM/Core/Config/MagicMerge.php @@ -240,10 +240,19 @@ public function __get($k) { $value = CRM_Utils_File::addTrailingSlash($value); if (isset($this->map[$k][2]) && in_array('mkdir', $this->map[$k][2])) { if (!is_dir($value) && !CRM_Utils_File::createDir($value, FALSE)) { - CRM_Core_Session::setStatus(ts('Failed to make directory (%1) at "%2". Please update the settings or file permissions.', [ + // we want to warn the user about this error + // ideally we show a browser alert + // but this might not be possible if the session handler isn't up yet, so fallback to just printing it (sorry) + $alertMessage = ts('Failed to make directory (%1) at "%2". Please update the settings or file permissions.', [ 1 => $k, 2 => $value, - ])); + ]); + try { + CRM_Core_Session::setStatus($alertMessage); + } + catch (\Error $e) { + echo $alertMessage; + } } } if (isset($this->map[$k][2]) && in_array('restrict', $this->map[$k][2])) { diff --git a/www/modules/civicrm/CRM/Core/Controller.php b/www/modules/civicrm/CRM/Core/Controller.php index 03db1e999..6bde93507 100644 --- a/www/modules/civicrm/CRM/Core/Controller.php +++ b/www/modules/civicrm/CRM/Core/Controller.php @@ -603,9 +603,12 @@ public function assign($var, $value = NULL) { * @param string $var * @param mixed $value * (reference) value of variable. + * + * @deprecated since 5.72 will be removed around 5.84 */ public function assign_by_ref($var, &$value) { - self::$_template->assign_by_ref($var, $value); + CRM_Core_Error::deprecatedFunctionWarning('assign'); + self::$_template->assign($var, $value); } /** diff --git a/www/modules/civicrm/CRM/Core/DAO.php b/www/modules/civicrm/CRM/Core/DAO.php index 98ac4c4c8..7c4e8414c 100644 --- a/www/modules/civicrm/CRM/Core/DAO.php +++ b/www/modules/civicrm/CRM/Core/DAO.php @@ -28,8 +28,6 @@ require_once 'PEAR.php'; require_once 'DB/DataObject.php'; -require_once 'CRM/Core/I18n.php'; - /** * Class CRM_Core_DAO */ @@ -42,13 +40,6 @@ class CRM_Core_DAO extends DB_DataObject { */ public static $_primaryKey = ['id']; - /** - * @return string[] - */ - protected function getPrimaryKey(): array { - return static::$_primaryKey; - } - /** * @return string */ @@ -58,7 +49,7 @@ protected function getFirstPrimaryKey(): string { // keys (which we support in codegen if not many other places) we return 'id' // simply because that is what we historically did & we don't want to 'just change' // it & break those extensions without doing the work to create an alternative. - return count($this->getPrimaryKey()) > 1 ? 'id' : $this->getPrimaryKey()[0]; + return count($this->keys()) > 1 ? 'id' : $this->keys()[0]; } /** @@ -149,7 +140,7 @@ protected function getFirstPrimaryKey(): string { */ public function __construct() { $this->initialize(); - $this->__table = $this->getTableName(); + $this->__table = $this::getLocaleTableName(); } /** @@ -160,7 +151,11 @@ public function __construct() { public static function getEntityTitle() { $className = static::class; CRM_Core_Error::deprecatedWarning("$className needs to be regenerated. Missing getEntityTitle method."); - return CRM_Core_DAO_AllCoreTables::getBriefName($className); + return CRM_Core_DAO_AllCoreTables::getEntityNameForClass($className); + } + + public static function getLabelField(): ?string { + return static::$_labelField; } /** @@ -191,10 +186,16 @@ public function __destruct() { /** * Returns the name of this table * + * Name is not localized, which is generally fine because localization happens when executing the query: + * @see \CRM_Core_I18n_Schema::rewriteQuery() + * + * To get the localized name of this table, + * @see self::getLocaleTableName() + * * @return string */ public static function getTableName() { - return self::getLocaleTableName(static::$_tableName ?? NULL); + return static::$_tableName ?? NULL; } /** @@ -206,6 +207,26 @@ public function getLog() { return static::$_log ?? FALSE; } + /** + * Returns the list of fields that can be imported + * + * @param bool $prefix + * @return array + */ + public static function import($prefix = FALSE) { + return CRM_Core_DAO_AllCoreTables::getImports(static::class, substr(static::getTableName(), 8), $prefix); + } + + /** + * Returns the list of fields that can be exported + * + * @param bool $prefix + * @return array + */ + public static function export($prefix = FALSE) { + return CRM_Core_DAO_AllCoreTables::getExports(static::class, substr(static::getTableName(), 8), $prefix); + } + /** * Initialize the DAO object. * @@ -441,11 +462,20 @@ public function reset() { } /** - * @param string $tableName + * Get localized name of this table, if applicable. + * + * If this is a multi-language installation and the table has localized columns, + * will return table name with language string appended, which points to a sql view. + * Otherwise, this returns the same output as + * @see self::getTableName() + * + * @param string|null $tableName + * Unnecessary deprecated param * * @return string */ - public static function getLocaleTableName($tableName) { + public static function getLocaleTableName($tableName = NULL) { + $tableName ??= static::getTableName(); global $dbLocale; if ($dbLocale) { $tables = CRM_Core_I18n_Schema::schemaStructureTables(); @@ -505,31 +535,24 @@ public function initialize() { } /** - * Defines the default key as 'id'. + * Returns primary keys (usually ['id']) * - * @return array + * @return string[] */ public function keys() { - static $keys; - if (!isset($keys)) { - $keys = ['id']; - } - return $keys; + return static::$_primaryKey; } /** * Tells DB_DataObject which keys use autoincrement. * 'id' is autoincrementing by default. * + * FIXME: this should return all autoincrement keys not just the first. * * @return array */ public function sequenceKey() { - static $sequenceKeys; - if (!isset($sequenceKeys)) { - $sequenceKeys = [$this->getFirstPrimaryKey(), TRUE]; - } - return $sequenceKeys; + return [$this->getFirstPrimaryKey(), TRUE]; } /** @@ -594,7 +617,7 @@ public static function getSupportedFields($checkPermissions = FALSE) { // Exclude fields yet not added by pending upgrades $dbVer = \CRM_Core_BAO_Domain::version(); - $daoExt = defined(static::class . '::EXT') ? constant(static::class . '::EXT') : NULL; + $daoExt = static::getExtensionName(); if ($fields && $daoExt === 'civicrm' && version_compare($dbVer, \CRM_Utils_System::version()) < 0) { $fields = array_filter($fields, function($field) use ($dbVer) { $add = $field['add'] ?? '1.0.0'; @@ -615,6 +638,59 @@ public static function getSupportedFields($checkPermissions = FALSE) { return $fields; } + /** + * Get name of extension in which this DAO is defined. + * @return string|null + */ + public static function getExtensionName(): ?string { + return defined(static::class . '::EXT') ? constant(static::class . '::EXT') : NULL; + } + + /** + * Format field values according to fields() metadata. + * + * When fetching results from a query, every field is returned as a string. + * This function automatically converts them to the correct data type. + * + * @param array $fieldValues + * @return void + */ + public static function formatFieldValues(array &$fieldValues) { + $fields = array_column((array) static::fields(), NULL, 'name'); + foreach ($fieldValues as $fieldName => $fieldValue) { + $fieldSpec = $fields[$fieldName] ?? NULL; + $fieldValues[$fieldName] = self::formatFieldValue($fieldValue, $fieldSpec); + } + } + + /** + * Format a value according to field metadata. + * + * @param string|null $value + * @param array|null $fieldSpec + * @return mixed + */ + protected static function formatFieldValue($value, ?array $fieldSpec) { + // DAO queries return `null` db values as empty string + if ($value === '' && empty($fieldSpec['required'])) { + return NULL; + } + if (!isset($value) || !isset($fieldSpec)) { + return $value; + } + $dataType = $fieldSpec['type'] ?? NULL; + if ($dataType === CRM_Utils_Type::T_INT) { + return (int) $value; + } + if ($dataType === CRM_Utils_Type::T_BOOLEAN) { + return (bool) $value; + } + if (!empty($fieldSpec['serialize'])) { + return self::unSerializeField($value, $fieldSpec['serialize']); + } + return $value; + } + /** * Get/set an associative array of table columns * @@ -771,11 +847,11 @@ public function copyValues($params) { $primaryKey = $this->getFirstPrimaryKey(); foreach ($this->fields() as $uniqueName => $field) { $dbName = $field['name']; - if (array_key_exists($dbName, $params)) { + if (is_array($params) && array_key_exists($dbName, $params)) { $value = $params[$dbName]; $exists = TRUE; } - elseif (array_key_exists($uniqueName, $params)) { + elseif (is_array($params) && array_key_exists($uniqueName, $params)) { $value = $params[$uniqueName]; $exists = TRUE; } @@ -958,7 +1034,7 @@ public static function writeRecord(array $record): CRM_Core_DAO { if ($className === 'CRM_Core_DAO') { throw new CRM_Core_Exception('Function writeRecord must be called on a subclass of CRM_Core_DAO'); } - $entityName = CRM_Core_DAO_AllCoreTables::getBriefName($className); + $entityName = CRM_Core_DAO_AllCoreTables::getEntityNameForClass($className); // For legacy reasons, empty values would sometimes be passed around as the string 'null'. // The DAO treats 'null' the same as '', and an empty string makes a lot more sense! @@ -989,7 +1065,7 @@ public static function writeRecord(array $record): CRM_Core_DAO { $instance->save(); if (!empty($record['custom']) && is_array($record['custom'])) { - CRM_Core_BAO_CustomValueTable::store($record['custom'], static::$_tableName, $instance->$idField, $op); + CRM_Core_BAO_CustomValueTable::store($record['custom'], static::getTableName(), $instance->$idField, $op); } \CRM_Utils_Hook::post($op, $entityName, $instance->$idField, $instance, $record); @@ -1027,7 +1103,7 @@ public static function deleteRecord(array $record) { if ($className === 'CRM_Core_DAO') { throw new CRM_Core_Exception('Function deleteRecord must be called on a subclass of CRM_Core_DAO'); } - $entityName = CRM_Core_DAO_AllCoreTables::getBriefName($className); + $entityName = CRM_Core_DAO_AllCoreTables::getEntityNameForClass($className); if (empty($record[$idField])) { throw new CRM_Core_Exception("Cannot delete {$entityName} with no $idField."); } @@ -1099,26 +1175,23 @@ public static function objectExists($value, $daoName, $daoID, $fieldName = 'name } /** - * Gets the names of all the tables in the schema. + * Gets the names of all enabled schema tables. + * + * - Includes tables from core, components & enabled extensions. + * - Excludes log tables, temp tables, and missing/disabled extensions. * * @return array * * @throws \CRM_Core_Exception */ public static function getTableNames(): array { - $dao = CRM_Core_DAO::executeQuery( - "SELECT TABLE_NAME - FROM information_schema.TABLES - WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME LIKE 'civicrm_%' - AND TABLE_NAME NOT LIKE '%_tmp%' - "); + // CRM_Core_DAO_AllCoreTables returns all tables with a dao (core + extensions) + $daoTables = array_column(CRM_Core_DAO_AllCoreTables::getEntities(), 'table'); - $values = []; - while ($dao->fetch()) { - $values[] = $dao->TABLE_NAME; - } - return $values; + // Include custom value tables + $customTables = array_column(CRM_Core_BAO_CustomGroup::getAll(), 'table_name'); + + return array_merge($daoTables, $customTables); } /** @@ -1179,14 +1252,10 @@ public static function checkConstraintExists($tableName, $constraint) { /** * Checks if CONSTRAINT keyword exists for a specified table. * - * @param array $tables - * - * @throws CRM_Core_Exception - * - * @return bool - * true if CONSTRAINT keyword exists, false otherwise + * @deprecated in 5.72 will be removed in 5.85 */ public static function schemaRequiresRebuilding($tables = ["civicrm_contact"]) { + CRM_Core_Error::deprecatedFunctionWarning('No alternative'); $show = []; foreach ($tables as $tableName) { if (!array_key_exists($tableName, $show)) { @@ -1215,15 +1284,10 @@ public static function schemaRequiresRebuilding($tables = ["civicrm_contact"]) { * Checks if the FK constraint name is in the format 'FK_tableName_columnName' * for a specified column of a table. * - * @param string $tableName - * @param string $columnName - * - * @return bool - * true if in format, false otherwise - * - * @throws \CRM_Core_Exception + * @deprecated in 5.72 will be removed in 5.85 */ public static function checkFKConstraintInFormat($tableName, $columnName) { + CRM_Core_Error::deprecatedFunctionWarning('No alternative'); static $show = []; if (!array_key_exists($tableName, $show)) { @@ -1244,14 +1308,10 @@ public static function checkFKConstraintInFormat($tableName, $columnName) { /** * Check whether a specific column in a specific table has always the same value. * - * @param string $tableName - * @param string $columnName - * @param string $columnValue - * - * @return bool - * true if the value is always $columnValue, false otherwise + * @deprecated in 5.72 will be removed in 5.85 */ public static function checkFieldHasAlwaysValue($tableName, $columnName, $columnValue) { + CRM_Core_Error::deprecatedFunctionWarning('APIv4'); $query = "SELECT * FROM $tableName WHERE $columnName != '$columnValue'"; $dao = CRM_Core_DAO::executeQuery($query); $result = $dao->fetch() ? FALSE : TRUE; @@ -1261,13 +1321,10 @@ public static function checkFieldHasAlwaysValue($tableName, $columnName, $column /** * Check whether a specific column in a specific table is always NULL. * - * @param string $tableName - * @param string $columnName - * - * @return bool - * true if if the value is always NULL, false otherwise + * @deprecated in 5.72 will be removed in 5.85 */ public static function checkFieldIsAlwaysNull($tableName, $columnName) { + CRM_Core_Error::deprecatedFunctionWarning('APIv4'); $query = "SELECT * FROM $tableName WHERE $columnName IS NOT NULL"; $dao = CRM_Core_DAO::executeQuery($query); $result = $dao->fetch() ? FALSE : TRUE; @@ -1282,13 +1339,25 @@ public static function checkFieldIsAlwaysNull($tableName, $columnName) { * @return bool * @throws CRM_Core_Exception */ - public static function tableHasBeenAdded() { + public static function tableHasBeenAdded(): bool { if (CRM_Utils_System::version() === CRM_Core_BAO_Domain::version()) { return TRUE; } - $daoExt = defined(static::class . '::EXT') ? constant(static::class . '::EXT') : NULL; - $daoVersion = defined(static::class . '::TABLE_ADDED') ? constant(static::class . '::TABLE_ADDED') : '1.0'; - return !($daoExt === 'civicrm' && version_compare(CRM_Core_BAO_Domain::version(), $daoVersion, '<')); + $daoExt = static::getExtensionName(); + if ($daoExt !== 'civicrm') { + // FIXME: Check extension tables + return TRUE; + } + $daoVersion = static::getTableAddVersion(); + return !(version_compare(CRM_Core_BAO_Domain::version(), $daoVersion, '<')); + } + + /** + * @return string + * Version in which table was added + */ + protected static function getTableAddVersion(): string { + return defined(static::class . '::TABLE_ADDED') ? constant(static::class . '::TABLE_ADDED') : '1.0'; } /** @@ -1322,17 +1391,6 @@ public static function checkTableHasData($tableName) { return $c > 0; } - /** - * @param $version - * @deprecated - * @return bool - */ - public function checkVersion($version) { - CRM_Core_Error::deprecatedFunctionWarning('CRM_Core_BAO_Domain::version'); - $dbVersion = CRM_Core_BAO_Domain::version(); - return trim($version) == trim($dbVersion); - } - /** * Find a DAO object for the given ID and return it. * @@ -1478,6 +1536,24 @@ public static function getFieldValue($daoName, $searchValue, $returnColumn = 'na return self::$_dbColumnValueCache[$daoName][$searchColumn][$searchValue][$returnColumn]; } + /** + * Fetch a single field value from the database. + * + * Uses static caching and applies formatting. + * + * @param string $returnColumn + * @param string|int $searchValue + * @param string $searchColumn + * @return array|bool|int|string|null + * Returned value will be formatted according to data type. + * @throws CRM_Core_Exception + */ + public static function getDbVal(string $returnColumn, $searchValue, string $searchColumn = 'id') { + $fieldSpec = static::getSupportedFields()[$returnColumn] ?? NULL; + $value = $fieldSpec ? self::getFieldValue(static::class, $searchValue, $returnColumn, $searchColumn) : NULL; + return self::formatFieldValue($value, $fieldSpec); + } + /** * Given a DAO name, a column name and a column value, find the record and SET the value of another column in that record * @@ -1512,15 +1588,11 @@ public static function setFieldValue($daoName, $searchValue, $setColumn, $setVal } /** - * Get sort string. - * - * @param array|object $sort either array or CRM_Utils_Sort - * @param string $default - * Default sort value. - * - * @return string + * Unused function. + * @deprecated in 5.72 will be removed in 5.85 */ public static function getSortString($sort, $default = NULL) { + CRM_Core_Error::deprecatedFunctionWarning('No alternative'); // check if sort is of type CRM_Utils_Sort if (is_a($sort, 'CRM_Utils_Sort')) { return $sort->orderBy(); @@ -1572,14 +1644,9 @@ public static function commonRetrieve($daoName, &$params, &$defaults, $returnPro } /** - * Delete the object records that are associated with this contact. - * - * @deprecated + * Unused function. * - * @param string $daoName - * Name of the dao object. - * @param int $contactId - * Id of the contact to delete. + * @deprecated in 5.47 will be removed in 5.80 */ public static function deleteEntityContact($daoName, $contactId) { CRM_Core_Error::deprecatedFunctionWarning('APIv4'); @@ -1965,7 +2032,7 @@ public static function copyGeneric($daoName, $criteria, $newData = NULL, $fields if (!$blockCopyofCustomValues) { $newObject->copyCustomFields($object->id, $newObject->id); } - CRM_Utils_Hook::post('create', CRM_Core_DAO_AllCoreTables::getBriefName($daoName), $newObject->id, $newObject); + CRM_Utils_Hook::post('create', CRM_Core_DAO_AllCoreTables::getEntityNameForClass($daoName), $newObject->id, $newObject); } return $newObject; @@ -2047,7 +2114,7 @@ protected function copyLocalizable($entityID, $newEntityID, $fieldsToPrefix, $fi * @param string $parentOperation */ public function copyCustomFields($entityID, $newEntityID, $parentOperation = NULL) { - $entity = CRM_Core_DAO_AllCoreTables::getBriefName(get_class($this)); + $entity = CRM_Core_DAO_AllCoreTables::getEntityNameForClass(get_class($this)); $tableName = CRM_Core_DAO_AllCoreTables::getTableForClass(get_class($this)); // Obtain custom values for the old entity. $customParams = $htmlType = []; @@ -2233,6 +2300,9 @@ public static function escapeString($string) { if ($string === NULL) { return ''; } + if (isset($GLOBALS['CIVICRM_SQL_ESCAPER'])) { + return call_user_func($GLOBALS['CIVICRM_SQL_ESCAPER'], $string); + } static $_dao = NULL; if (!$_dao) { // If this is an atypical case (e.g. preparing .sql file before CiviCRM @@ -2610,13 +2680,9 @@ public function getReferenceCounts() { } /** - * List all tables which have hard foreign keys to this table. - * - * For now, this returns a description of every entity_id/entity_table - * reference. - * TODO: filter dynamic entity references on the $tableName, based on - * schema metadata in dynamicForeignKey which enumerates a restricted - * set of possible entity_table's. + * List all tables which have either: + * - hard foreign keys to this table, or + * - a dynamic foreign key that includes this table as a possible target. * * @param string $tableName * Table referred to. @@ -2652,9 +2718,6 @@ public static function getReferencesToTable($tableName) { * @throws \CRM_Core_Exception */ public static function getReferencesToContactTable() { - if (isset(\Civi::$statics[__CLASS__]) && isset(\Civi::$statics[__CLASS__]['contact_references'])) { - return \Civi::$statics[__CLASS__]['contact_references']; - } $contactReferences = []; $coreReferences = CRM_Core_DAO::getReferencesToTable('civicrm_contact'); foreach ($coreReferences as $coreReference) { @@ -2669,8 +2732,7 @@ public static function getReferencesToContactTable() { } self::appendCustomTablesExtendingContacts($contactReferences); self::appendCustomContactReferenceFields($contactReferences); - \Civi::$statics[__CLASS__]['contact_references'] = $contactReferences; - return \Civi::$statics[__CLASS__]['contact_references']; + return $contactReferences; } /** @@ -2696,40 +2758,38 @@ public static function getDynamicReferencesToTable($tableName) { /** * Add custom tables that extend contacts to the list of contact references. * - * CRM_Core_BAO_CustomGroup::getAllCustomGroupsByBaseEntity seems like a safe-ish - * function to be sure all are retrieved & we don't miss subtypes or inactive or multiples - * - the down side is it is not cached. - * - * Further changes should be include tests in the CRM_Core_MergerTest class - * to ensure that disabled, subtype, multiple etc groups are still captured. + * @internal + * Includes all contact custom groups including inactive, multiple & subtypes. * * @param array $cidRefs */ public static function appendCustomTablesExtendingContacts(&$cidRefs) { - $customValueTables = CRM_Core_BAO_CustomGroup::getAllCustomGroupsByBaseEntity('Contact'); - $customValueTables->find(); - while ($customValueTables->fetch()) { - $cidRefs[$customValueTables->table_name][] = 'entity_id'; + $customGroups = CRM_Core_BAO_CustomGroup::getAll(['extends' => 'Contact']); + foreach ($customGroups as $customGroup) { + $cidRefs[$customGroup['table_name']][] = 'entity_id'; } } /** - * Add custom ContactReference fields to the list of contact references + * Add custom ContactReference fields to the list of contact references. * - * This includes active and inactive fields/groups + * @internal + * Includes both ContactReference and EntityReference type fields. + * Includes active and inactive fields/groups * * @param array $cidRefs - * - * @throws \CRM_Core_Exception */ public static function appendCustomContactReferenceFields(&$cidRefs) { - $fields = civicrm_api3('CustomField', 'get', [ - 'return' => ['column_name', 'custom_group_id.table_name'], - 'data_type' => 'ContactReference', - 'options' => ['limit' => 0], - ])['values']; - foreach ($fields as $field) { - $cidRefs[$field['custom_group_id.table_name']][] = $field['column_name']; + $contactTypes = array_merge(['Contact'], CRM_Contact_BAO_ContactType::basicTypes(TRUE)); + foreach (CRM_Core_BAO_CustomGroup::getAll() as $customGroup) { + foreach ($customGroup['fields'] as $field) { + if ( + $field['data_type'] === 'ContactReference' || + in_array($field['fk_entity'], $contactTypes, TRUE) + ) { + $cidRefs[$customGroup['table_name']][] = $field['column_name']; + } + } } } @@ -3158,9 +3218,9 @@ protected static function getDynamicFkAclClauses(string $entityTableField, strin $allTableNames = CRM_Core_DAO_AllCoreTables::tables(); $relatedEntities = array_intersect_key(array_flip((array) $entityTableValues), $allTableNames); } - // No valid entity_table in WHERE clause so build an ACL case for every possible entity type + // No valid entity_table in WHERE clause so build an ACL case for every enabled entity type if (empty($relatedEntities)) { - $relatedEntities = static::buildOptions($entityTableField, 'get'); + $relatedEntities = static::buildOptions($entityTableField, 'create'); } // Hmm, this entity is missing entity_table pseudoconstant. We really should fix that. if (!$relatedEntities) { @@ -3170,6 +3230,10 @@ protected static function getDynamicFkAclClauses(string $entityTableField, strin foreach ($relatedEntities as $table => $ent) { // Ensure $ent is the machine name of the entity not a translated title $ent = CRM_Core_DAO_AllCoreTables::getEntityNameForTable($table); + // Skip if entity doesn't exist. This shouldn't happen, but better safe than sorry. + if (!$ent) { + continue; + } // Prevent infinite recursion $subquery = $table === static::getTableName() ? NULL : CRM_Utils_SQL::mergeSubquery($ent); if ($subquery) { @@ -3210,7 +3274,7 @@ protected static function getDynamicFkAclClauses(string $entityTableField, strin public static function getSelectWhereClause($tableAlias = NULL, $entityName = NULL, $conditions = []) { $bao = new static(); $tableAlias ??= $bao->tableName(); - $entityName ??= CRM_Core_DAO_AllCoreTables::getBriefName(get_class($bao)); + $entityName ??= CRM_Core_DAO_AllCoreTables::getEntityNameForClass(get_class($bao)); $finalClauses = []; $fields = static::getSupportedFields(); $selectWhereClauses = $bao->addSelectWhereClause($entityName, NULL, $conditions); @@ -3396,7 +3460,7 @@ public static function getEntityPaths() { /** * Overridable function to get icon for a particular entity. * - * Example: `CRM_Contact_BAO_Contact::getIcon('Contact', 123)` + * Example: `CRM_Contact_BAO_Contact::getEntityIcon('Contact', 123)` * * @param string $entityName * Short name of the entity. This may seem redundant because the entity name can usually be inferred @@ -3405,10 +3469,7 @@ public static function getEntityPaths() { * Id of the entity. * @throws CRM_Core_Exception */ - public static function getEntityIcon(string $entityName, int $entityId) { - if (static::class === 'CRM_Core_DAO' || static::class !== CRM_Core_DAO_AllCoreTables::getBAOClassName(static::class)) { - throw new CRM_Core_Exception('CRM_Core_DAO::getIcon must be called on a BAO class e.g. CRM_Contact_BAO_Contact::getIcon("Contact", 123).'); - } + public static function getEntityIcon(string $entityName, int $entityId = NULL): ?string { // By default, just return the icon representing this entity. If there's more complex lookup to do, // the BAO for this entity should override this method. return static::$_icon; @@ -3434,7 +3495,7 @@ private function makeNameFromLabel(): void { // No unique index on "name", do nothing return; } - $labelField = $this::$_labelField; + $labelField = $this::getLabelField(); $label = $this->$labelField ?? NULL; if (!$label && $label !== '0') { // No label supplied, do nothing diff --git a/www/modules/civicrm/CRM/Core/DAO/ActionLog.php b/www/modules/civicrm/CRM/Core/DAO/ActionLog.php index b7e57e2ff..1718e9d49 100644 --- a/www/modules/civicrm/CRM/Core/DAO/ActionLog.php +++ b/www/modules/civicrm/CRM/Core/DAO/ActionLog.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/ActionLog.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:281e8a8a8378ddbbf65de5cfacc97828) + * (GenCodeChecksum:fb407a1919e8bb230001ccab90250e6c) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/ActionSchedule.php b/www/modules/civicrm/CRM/Core/DAO/ActionSchedule.php index 3078d4aa9..5127e5e44 100644 --- a/www/modules/civicrm/CRM/Core/DAO/ActionSchedule.php +++ b/www/modules/civicrm/CRM/Core/DAO/ActionSchedule.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/ActionSchedule.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:155066f04857fec33b9f6b5d9d422cd9) + * (GenCodeChecksum:4b98974a043a37d8dcef7b8b0e786c03) */ /** @@ -1324,7 +1324,7 @@ public static function &fields() { 'entity' => 'ActionSchedule', 'bao' => 'CRM_Core_BAO_ActionSchedule', 'localizable' => 0, - 'FKClassName' => 'CRM_SMS_DAO_Provider', + 'FKClassName' => 'CRM_SMS_DAO_SmsProvider', 'FKColumnName' => 'id', 'html' => [ 'type' => 'Select', diff --git a/www/modules/civicrm/CRM/Core/DAO/Address.php b/www/modules/civicrm/CRM/Core/DAO/Address.php index 2d3b5ce05..8bd3d10d9 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Address.php +++ b/www/modules/civicrm/CRM/Core/DAO/Address.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Address.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:8ab653268a837a495427e04e0f874af7) + * (GenCodeChecksum:ca74689a56714fad7461c670a9291ee3) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/AddressFormat.php b/www/modules/civicrm/CRM/Core/DAO/AddressFormat.php index 2ebd17a51..550e12fef 100644 --- a/www/modules/civicrm/CRM/Core/DAO/AddressFormat.php +++ b/www/modules/civicrm/CRM/Core/DAO/AddressFormat.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/AddressFormat.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:688ab6fa7cf9ccd7cddaab899d4abcda) + * (GenCodeChecksum:2a3b2f3e967533cc5edca28e50d0a8cf) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/AllCoreTables.data.php b/www/modules/civicrm/CRM/Core/DAO/AllCoreTables.data.php index 31b51fb7a..fdbfcc18c 100644 --- a/www/modules/civicrm/CRM/Core/DAO/AllCoreTables.data.php +++ b/www/modules/civicrm/CRM/Core/DAO/AllCoreTables.data.php @@ -467,9 +467,9 @@ 'class' => 'CRM_Financial_DAO_PaymentToken', 'table' => 'civicrm_payment_token', ], - 'CRM_SMS_DAO_Provider' => [ - 'name' => 'Provider', - 'class' => 'CRM_SMS_DAO_Provider', + 'CRM_SMS_DAO_SmsProvider' => [ + 'name' => 'SmsProvider', + 'class' => 'CRM_SMS_DAO_SmsProvider', 'table' => 'civicrm_sms_provider', ], 'CRM_Member_DAO_MembershipType' => [ diff --git a/www/modules/civicrm/CRM/Core/DAO/AllCoreTables.php b/www/modules/civicrm/CRM/Core/DAO/AllCoreTables.php index ba28171ba..c76cede17 100644 --- a/www/modules/civicrm/CRM/Core/DAO/AllCoreTables.php +++ b/www/modules/civicrm/CRM/Core/DAO/AllCoreTables.php @@ -10,6 +10,8 @@ +--------------------------------------------------------------------+ */ +use Civi\Schema\EntityRepository; + /** * * @package CRM @@ -17,89 +19,75 @@ */ class CRM_Core_DAO_AllCoreTables { - private static $tables = NULL; - private static $daoToClass = NULL; - private static $entityTypes = NULL; - /** - * Initialise. + * @deprecated in 5.73 will be removed in 5.90 * * @param bool $fresh Deprecated parameter, use flush() to flush. */ public static function init(bool $fresh = FALSE): void { - if (!empty(Civi::$statics[__CLASS__]['initialised']) && !$fresh) { - return; - } + CRM_Core_Error::deprecatedFunctionWarning('CRM_Core_DAO_AllCoreTables::flush()'); if ($fresh) { - CRM_Core_Error::deprecatedWarning('Use CRM_Core_DAO_AllCoreTables::flush()'); + EntityRepository::flush(); } - - Civi::$statics[__CLASS__] = []; - - $file = preg_replace('/\.php$/', '.data.php', __FILE__); - $entityTypes = require $file; - CRM_Utils_Hook::entityTypes($entityTypes); - - self::$entityTypes = []; - self::$tables = []; - self::$daoToClass = []; - foreach ($entityTypes as $entityType) { - self::registerEntityType( - $entityType['name'], - $entityType['class'], - $entityType['table'], - $entityType['fields_callback'] ?? NULL, - $entityType['links_callback'] ?? NULL - ); - } - - Civi::$statics[__CLASS__]['initialised'] = TRUE; } /** * Flush class cache. */ public static function flush(): void { - Civi::$statics[__CLASS__]['initialised'] = FALSE; + EntityRepository::flush(); } /** - * (Quasi-Private) Do not call externally (except for unit-testing) + * @return array[] + * [EntityName => [table => table_name, class => CRM_DAO_ClassName]][] + */ + public static function getEntities(): array { + return EntityRepository::getEntities(); + } + + /** + * @return string[] + * [table_name => EntityName][] + */ + private static function getEntitiesByTable(): array { + return EntityRepository::getTableIndex(); + } + + /** + * This one is problematic because it's not strictly required to have one class + * per table. It's possible for multiple tables to share a class. * - * @param string $briefName - * @param string $className - * @param string $tableName - * @param string $fields_callback - * @param string $links_callback + * @return string[] + * [CRM_DAO_ClassName => EntityName] */ - public static function registerEntityType($briefName, $className, $tableName, $fields_callback = NULL, $links_callback = NULL) { - self::$daoToClass[$briefName] = $className; - self::$tables[$tableName] = $className; - self::$entityTypes[$briefName] = [ - 'name' => $briefName, - 'class' => $className, - 'table' => $tableName, - 'fields_callback' => $fields_callback, - 'links_callback' => $links_callback, - ]; + private static function getEntitiesByClass(): array { + return EntityRepository::getClassIndex(); } /** - * @return array - * Ex: $result['Contact']['table'] == 'civicrm_contact'; + * @deprecated in 5.72 will be removed in 5.90. */ public static function get() { - self::init(); - return self::$entityTypes; + CRM_Core_Error::deprecatedFunctionWarning('CRM_Core_DAO_AllCoreTables::getEntities'); + $entities = []; + foreach (self::getEntities() as $name => $entity) { + $entities[$name] = $entity + [ + 'name' => $name, + 'fields_callback' => $entity['fields_callback'] ?? NULL, + 'links_callback' => $entity['links_callback'] ?? NULL, + ]; + } + return $entities; } /** - * @return array - * List of SQL table names. + * Mapping from table-names to class-names. + * @return string[] + * [table_name => CRM_DAO_ClassName] */ public static function tables() { - self::init(); - return self::$tables; + return array_column(self::getEntities(), 'class', 'table'); } /** @@ -108,10 +96,9 @@ public static function tables() { */ public static function indices($localize = TRUE) { $indices = []; - self::init(); - foreach (self::$daoToClass as $class) { - if (is_callable([$class, 'indices'])) { - $indices[$class::getTableName()] = $class::indices($localize); + foreach (self::getEntities() as $entity) { + if (is_callable([$entity['class'], 'indices'])) { + $indices[$entity['class']::getTableName()] = $entity['class']::indices($localize); } } return $indices; @@ -165,21 +152,19 @@ public static function multilingualize($class, $originalIndices) { } /** - * @return array - * Mapping from brief-names to class-names. - * Ex: $result['Contact'] == 'CRM_Contact_DAO_Contact'. + * Mapping from entity-names to class-names. + * @return string[] + * [EntityName => CRM_DAO_ClassName] */ public static function daoToClass() { - self::init(); - return self::$daoToClass; + return array_combine(array_keys(self::getEntities()), array_column(self::getEntities(), 'class')); } /** - * @return array - * Mapping from table-names to class-names. - * Ex: $result['civicrm_contact'] == 'CRM_Contact_DAO_Contact'. + * @deprecated in 5.72 will be removed in 5.90 */ public static function getCoreTables() { + CRM_Core_Error::deprecatedFunctionWarning('CRM_Core_DAO_AllCoreTables::tables'); return self::tables(); } @@ -190,7 +175,7 @@ public static function getCoreTables() { * @return bool */ public static function isCoreTable($tableName) { - return array_key_exists($tableName, self::tables()); + return array_key_exists($tableName, self::getEntitiesByTable()); } /** @@ -283,21 +268,21 @@ public static function convertEntityNameToLower(string $name): string { * List of class names. */ public static function getClasses() { - return array_values(self::daoToClass()); + return array_keys(self::getEntitiesByClass()); } /** - * Get a list of all extant BAO classes. + * Get a list of all extant BAO classes, keyed by entityName. * - * @return array - * Ex: ['Contact' => 'CRM_Contact_BAO_Contact'] + * @return string[] + * [EntityName => CRM_BAO_ClassName] */ public static function getBaoClasses() { $r = []; - foreach (\CRM_Core_DAO_AllCoreTables::daoToClass() as $entity => $daoClass) { - $baoClass = str_replace('_DAO_', '_BAO_', $daoClass); + foreach (self::getEntities() as $name => $entity) { + $baoClass = str_replace('_DAO_', '_BAO_', $entity['class']); if (class_exists($baoClass)) { - $r[$entity] = $baoClass; + $r[$name] = $baoClass; } } return $r; @@ -315,33 +300,48 @@ public static function getClassForTable(string $tableName) { global $dbLocale; $tableName = str_replace($dbLocale, '', $tableName); } - return self::tables()[$tableName] ?? NULL; + $entityName = self::getEntitiesByTable()[$tableName] ?? ''; + return self::getEntities()[$entityName]['class'] ?? NULL; } /** - * Given a brief-name, determine the full class-name. + * Given an entity name, determine the DAO class-name. * - * @param string $briefName + * @param string|null $entityName * Ex: 'Contact'. * @return string|CRM_Core_DAO|NULL * Ex: 'CRM_Contact_DAO_Contact'. */ - public static function getFullName($briefName) { - self::init(); - return self::$entityTypes[$briefName]['class'] ?? NULL; + public static function getDAONameForEntity(?string $entityName) { + return self::getEntities()[$entityName]['class'] ?? NULL; } /** - * Given a full class-name, determine the brief-name. + * @deprecated in 5.72 will be removed in 5.96 + */ + public static function getFullName($entityName) { + CRM_Core_Error::deprecatedFunctionWarning('CRM_Core_DAO_AllCoreTables::getDAONameForEntity'); + return self::getDAONameForEntity((string) $entityName); + } + + /** + * Given a DAO or BAO class-name, return the entity name. * - * @param string $className + * @param string|null $className * Ex: 'CRM_Contact_DAO_Contact'. * @return string|NULL * Ex: 'Contact'. */ - public static function getBriefName($className) { + public static function getEntityNameForClass(?string $className): ?string { $className = self::getCanonicalClassName($className); - return array_search($className, self::daoToClass(), TRUE) ?: NULL; + return self::getEntitiesByClass()[$className] ?? NULL; + } + + /** + * @deprecated in 5.72 will be removed in 5.102 + */ + public static function getBriefName($className): ?string { + return self::getEntityNameForClass((string) $className); } /** @@ -349,127 +349,115 @@ public static function getBriefName($className) { * @return string|FALSE SQL table name */ public static function getTableForClass($className) { - return array_search(self::getCanonicalClassName($className), - self::tables()); + $entityName = self::getEntityNameForClass($className); + return self::getEntities()[$entityName]['table'] ?? FALSE; } /** * Convert the entity name into a table name. * - * @param string $briefName + * @param string $entityName + * e.g. 'Activity' * * @return string + * e.g. 'civicrm_activity' */ - public static function getTableForEntityName($briefName): string { - self::init(); - return self::$entityTypes[$briefName]['table']; + public static function getTableForEntityName($entityName): string { + return self::getEntities()[$entityName]['table']; } /** - * Convert table name to brief entity name. + * Convert table name to entity name. * * @param string $tableName * * @return FALSE|string */ public static function getEntityNameForTable(string $tableName) { - self::init(); // CRM-19677: on multilingual setup, trim locale from $tableName to fetch class name if (CRM_Core_I18n::isMultilingual()) { global $dbLocale; $tableName = str_replace($dbLocale, '', $tableName); } - $matches = CRM_Utils_Array::findAll(self::$entityTypes, ['table' => $tableName]); - return $matches ? $matches[0]['name'] : NULL; + return self::getEntitiesByTable()[$tableName] ?? NULL; } /** - * Reinitialise cache. - * - * @deprecated + * @deprecated in 5.54 will be removed in 5.85 */ public static function reinitializeCache(): void { + CRM_Core_Error::deprecatedFunctionWarning('CRM_Core_DAO_AllCoreTables::flush'); self::flush(); } /** * (Quasi-Private) Do not call externally. For use by DAOs. * - * @param string $dao + * @param string|CRM_Core_DAO $dao * Ex: 'CRM_Core_DAO_Address'. * @param string $labelName * Ex: 'address'. * @param bool $prefix * @param array $foreignDAOs + * Historically used for... something? Currently never set by any core BAO. * @return array + * @internal */ - public static function getExports($dao, $labelName, $prefix, $foreignDAOs) { - // Bug-level compatibility -- or sane behavior? - $cacheKey = $dao . ':export'; - // $cacheKey = $dao . ':' . ($prefix ? 'export-prefix' : 'export'); - - if (!isset(Civi::$statics[__CLASS__][$cacheKey])) { - $exports = []; - $fields = $dao::fields(); - - foreach ($fields as $name => $field) { - if (!empty($field['export'])) { - if ($prefix) { - $exports[$labelName] = & $fields[$name]; - } - else { - $exports[$name] = & $fields[$name]; - } - } - } + public static function getExports($dao, $labelName, $prefix, $foreignDAOs = []) { + $exports = []; - foreach ($foreignDAOs as $foreignDAO) { - $exports = array_merge($exports, $foreignDAO::export(TRUE)); + foreach ($dao::fields() as $name => $field) { + if (!empty($field['export'])) { + if ($prefix) { + $exports[$labelName] = $field; + } + else { + $exports[$name] = $field; + } } + } - Civi::$statics[__CLASS__][$cacheKey] = $exports; + // TODO: Remove this bit; no core DAO actually uses it + foreach ($foreignDAOs as $foreignDAO) { + $exports = array_merge($exports, $foreignDAO::export(TRUE)); } - return Civi::$statics[__CLASS__][$cacheKey]; + + return $exports; } /** * (Quasi-Private) Do not call externally. For use by DAOs. * - * @param string $dao + * @param string|CRM_Core_DAO $dao * Ex: 'CRM_Core_DAO_Address'. * @param string $labelName * Ex: 'address'. * @param bool $prefix * @param array $foreignDAOs + * Historically used for... something? Currently never set by any core BAO. * @return array + * @internal */ - public static function getImports($dao, $labelName, $prefix, $foreignDAOs) { - // Bug-level compatibility -- or sane behavior? - $cacheKey = $dao . ':import'; - // $cacheKey = $dao . ':' . ($prefix ? 'import-prefix' : 'import'); - - if (!isset(Civi::$statics[__CLASS__][$cacheKey])) { - $imports = []; - $fields = $dao::fields(); - - foreach ($fields as $name => $field) { - if (!empty($field['import'])) { - if ($prefix) { - $imports[$labelName] = & $fields[$name]; - } - else { - $imports[$name] = & $fields[$name]; - } - } - } + public static function getImports($dao, $labelName, $prefix, $foreignDAOs = []): array { + $imports = []; - foreach ($foreignDAOs as $foreignDAO) { - $imports = array_merge($imports, $foreignDAO::import(TRUE)); + foreach ($dao::fields() as $name => $field) { + if (!empty($field['import'])) { + if ($prefix) { + $imports[$labelName] = $field; + } + else { + $imports[$name] = $field; + } } + } - Civi::$statics[__CLASS__][$cacheKey] = $imports; + // TODO: Remove this bit; no core DAO actually uses it + foreach ($foreignDAOs as $foreignDAO) { + $imports = array_merge($imports, $foreignDAO::import(TRUE)); } - return Civi::$statics[__CLASS__][$cacheKey]; + + return $imports; } /** @@ -477,18 +465,19 @@ public static function getImports($dao, $labelName, $prefix, $foreignDAOs) { * * Apply any third-party alterations to the `fields()`. * - * TODO: This function should probably take briefName as the key instead of className + * TODO: This function should probably take entityName as the key instead of className * because the latter is not always unique (e.g. virtual entities) * * @param string $className * @param string $event * @param mixed $values + * @internal */ public static function invoke($className, $event, &$values) { - self::init(); - $briefName = self::getBriefName($className); - if (isset(self::$entityTypes[$briefName][$event])) { - foreach (self::$entityTypes[$briefName][$event] as $filter) { + $entityName = self::getEntityNameForClass($className); + $entityTypes = self::getEntities(); + if (isset($entityTypes[$entityName][$event])) { + foreach ($entityTypes[$entityName][$event] as $filter) { $args = [$className, &$values]; \Civi\Core\Resolver::singleton()->call($filter, $args); } diff --git a/www/modules/civicrm/CRM/Core/DAO/Base.php b/www/modules/civicrm/CRM/Core/DAO/Base.php new file mode 100644 index 000000000..4982e59f4 --- /dev/null +++ b/www/modules/civicrm/CRM/Core/DAO/Base.php @@ -0,0 +1,267 @@ + $field) { + if (!empty($field['primary_key'])) { + $keys[] = $name; + } + } + return $keys; + } + + public static function getEntityTitle($plural = FALSE) { + $info = static::getEntityInfo(); + return ($plural && isset($info['title_plural'])) ? $info['title_plural'] : $info['title']; + } + + /** + * @inheritDoc + */ + public static function getEntityPaths(): array { + $definition = static::getEntityDefinition(); + if (isset($definition['getPaths'])) { + return $definition['getPaths'](); + } + return []; + } + + public static function getLabelField(): ?string { + return static::getEntityInfo()['label_field'] ?? NULL; + } + + /** + * @inheritDoc + */ + public static function getEntityDescription(): ?string { + return static::getEntityInfo()['description'] ?? NULL; + } + + /** + * @inheritDoc + */ + public static function getTableName() { + return static::getEntityDefinition()['table']; + } + + /** + * @inheritDoc + */ + public function getLog(): bool { + return static::getEntityInfo()['log'] ?? FALSE; + } + + /** + * @inheritDoc + */ + public static function getEntityIcon(string $entityName, int $entityId = NULL): ?string { + return static::getEntityInfo()['icon'] ?? NULL; + } + + /** + * @inheritDoc + */ + protected static function getTableAddVersion(): string { + return static::getEntityInfo()['add'] ?? '1.0'; + } + + /** + * @inheritDoc + */ + public static function getExtensionName(): ?string { + return static::getEntityDefinition()['module']; + } + + /** + * @inheritDoc + */ + public static function &fields() { + $fields = []; + foreach (static::getSchemaFields() as $field) { + $key = $field['uniqueName'] ?? $field['name']; + unset($field['uniqueName']); + $fields[$key] = $field; + } + return $fields; + } + + private static function getSchemaFields(): array { + return (Civi::$statics[static::class]['fields'] ??= static::loadSchemaFields()); + } + + private static function loadSchemaFields(): array { + $fields = []; + $entityDef = static::getEntityDefinition(); + $baoName = CRM_Core_DAO_AllCoreTables::getBAOClassName(static::class); + + foreach ($entityDef['getFields']() as $fieldName => $fieldSpec) { + $field = [ + 'name' => $fieldName, + 'type' => !empty($fieldSpec['data_type']) ? \CRM_Utils_Type::getValidTypes()[$fieldSpec['data_type']] : constant(\CRM_Utils_Schema::getCrmTypeFromSqlType($fieldSpec['sql_type'])), + 'title' => $fieldSpec['title'], + 'description' => $fieldSpec['description'] ?? NULL, + ]; + if (!empty($fieldSpec['required'])) { + $field['required'] = TRUE; + } + if (str_starts_with($fieldSpec['sql_type'], 'decimal(')) { + $precision = self::getFieldLength($fieldSpec['sql_type']); + $field['precision'] = explode(',', $precision); + } + foreach (['maxlength', 'size', 'rows', 'cols'] as $attr) { + if (isset($fieldSpec['input_attrs'][$attr])) { + $field[$attr] = $fieldSpec['input_attrs'][$attr]; + unset($fieldSpec['input_attrs'][$attr]); + } + } + if (!isset($field['size']) && str_contains($fieldSpec['sql_type'], 'char')) { + $length = self::getFieldLength($fieldSpec['sql_type']); + $field['size'] = constant(CRM_Utils_Schema::getDefaultSize($length)); + } + $usage = $fieldSpec['usage'] ?? []; + $field['usage'] = [ + 'import' => in_array('import', $usage), + 'export' => in_array('export', $usage), + 'duplicate_matching' => in_array('duplicate_matching', $usage), + 'token' => in_array('token', $usage), + ]; + if ($field['usage']['import']) { + $field['import'] = TRUE; + } + $field['where'] = $entityDef['table'] . '.' . $field['name']; + if ($field['usage']['export'] || (!$field['usage']['export'] && $field['usage']['import'])) { + $field['export'] = $field['usage']['export']; + } + if (!empty($fieldSpec['contact_type'])) { + $field['contactType'] = $fieldSpec['contact_type']; + } + if (!empty($fieldSpec['permission'])) { + $field['permission'] = $fieldSpec['permission']; + } + if (array_key_exists('default', $fieldSpec)) { + $field['default'] = isset($fieldSpec['default']) ? (string) $fieldSpec['default'] : NULL; + if (is_bool($fieldSpec['default'])) { + $field['default'] = $fieldSpec['default'] ? '1' : '0'; + } + } + $field['table_name'] = $entityDef['table']; + $field['entity'] = $entityDef['name']; + $field['bao'] = $baoName; + $field['localizable'] = intval($fieldSpec['localizable'] ?? 0); + if (!empty($fieldSpec['localize_context'])) { + $field['localize_context'] = (string) $fieldSpec['localize_context']; + } + if (!empty($fieldSpec['entity_reference'])) { + if (!empty($fieldSpec['entity_reference']['entity'])) { + $field['FKClassName'] = CRM_Core_DAO_AllCoreTables::getDAONameForEntity($fieldSpec['entity_reference']['entity']); + } + if (!empty($fieldSpec['entity_reference']['dynamic_entity'])) { + $field['DFKEntityColumn'] = $fieldSpec['entity_reference']['dynamic_entity']; + } + $field['FKColumnName'] = $fieldSpec['entity_reference']['key'] ?? 'id'; + } + if (!empty($fieldSpec['component'])) { + $field['component'] = $fieldSpec['component']; + } + if (!empty($fieldSpec['serialize'])) { + $field['serialize'] = $fieldSpec['serialize']; + } + if (!empty($fieldSpec['unique_name'])) { + $field['uniqueName'] = $fieldSpec['unique_name']; + } + if (!empty($fieldSpec['unique_title'])) { + $field['uniqueTitle'] = $fieldSpec['unique_title']; + } + if (!empty($fieldSpec['deprecated'])) { + $field['deprecated'] = TRUE; + } + if (!empty($fieldSpec['input_attrs'])) { + $field['html'] = CRM_Utils_Array::rekey($fieldSpec['input_attrs'], fn($str) => CRM_Utils_String::convertStringToCamel($str, FALSE)); + } + if (!empty($fieldSpec['input_type'])) { + $field['html']['type'] = $fieldSpec['input_type']; + } + if (!empty($fieldSpec['pseudoconstant'])) { + $field['pseudoconstant'] = CRM_Utils_Array::rekey($fieldSpec['pseudoconstant'], fn($str) => CRM_Utils_String::convertStringToCamel($str, FALSE)); + if (!isset($field['pseudoconstant']['optionEditPath']) && !empty($field['pseudoconstant']['optionGroupName'])) { + $field['pseudoconstant']['optionEditPath'] = 'civicrm/admin/options/' . $field['pseudoconstant']['optionGroupName']; + } + } + if (!empty($fieldSpec['primary_key']) || !empty($field['readonly'])) { + $field['readonly'] = TRUE; + } + $field['add'] = $fieldSpec['add'] ?? NULL; + $fields[$fieldName] = $field; + } + CRM_Core_DAO_AllCoreTables::invoke(static::class, 'fields_callback', $fields); + return $fields; + } + + private static function getFieldLength($sqlType): ?string { + $open = strpos($sqlType, '('); + if ($open) { + return substr($sqlType, $open + 1, -1); + } + return NULL; + } + + /** + * @inheritDoc + */ + public static function indices(bool $localize = TRUE): array { + $definition = static::getEntityDefinition(); + $indices = []; + if (isset($definition['getIndices'])) { + $fields = $definition['getFields'](); + foreach ($definition['getIndices']() as $name => $info) { + $index = [ + 'name' => $name, + 'field' => [], + 'localizable' => FALSE, + ]; + foreach ($info['fields'] as $fieldName => $length) { + if (!empty($fields[$fieldName]['localizable'])) { + $index['localizable'] = TRUE; + } + if (is_int($length)) { + $fieldName .= "($length)"; + } + $index['field'][] = $fieldName; + } + if (!empty($info['unique'])) { + $index['unique'] = TRUE; + } + $index['sig'] = ($definition['table']) . '::' . intval($info['unique'] ?? 0) . '::' . implode('::', $index['field']); + $indices[$name] = $index; + } + } + return ($localize && $indices) ? CRM_Core_DAO_AllCoreTables::multilingualize(static::class, $indices) : $indices; + } + + private static function getEntityDefinition(): array { + $entityName = CRM_Core_DAO_AllCoreTables::getEntityNameForClass(static::class); + return \Civi\Schema\EntityRepository::getEntity($entityName); + } + + private static function getEntityInfo(): array { + return static::getEntityDefinition()['getInfo'](); + } + +} diff --git a/www/modules/civicrm/CRM/Core/DAO/Cache.php b/www/modules/civicrm/CRM/Core/DAO/Cache.php index dcb29e16f..26bcfea08 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Cache.php +++ b/www/modules/civicrm/CRM/Core/DAO/Cache.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Cache.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:b61a713809b89c9f8bf1bbd6256ef986) + * (GenCodeChecksum:403c445d5664ce00dd56a61dc7e18bc6) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/Component.php b/www/modules/civicrm/CRM/Core/DAO/Component.php index e0dd689bd..4a857a352 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Component.php +++ b/www/modules/civicrm/CRM/Core/DAO/Component.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Component.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:fdf9249dc62d2c5495f09af4695d8820) + * (GenCodeChecksum:b14371bb8082633c09cf59226edaa213) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/Country.php b/www/modules/civicrm/CRM/Core/DAO/Country.php index b9ff3e973..fc888e647 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Country.php +++ b/www/modules/civicrm/CRM/Core/DAO/Country.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Country.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:0a6cc17a97d1804a8ef0634e097b6315) + * (GenCodeChecksum:3c4767c24868f7bfce24636b1c5f1d18) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/County.php b/www/modules/civicrm/CRM/Core/DAO/County.php index bac95d750..82b0527dc 100644 --- a/www/modules/civicrm/CRM/Core/DAO/County.php +++ b/www/modules/civicrm/CRM/Core/DAO/County.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/County.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:5872e0fad72f1657c21120cb8de7f834) + * (GenCodeChecksum:3c3c1fed6b777fff9171e106c72678b3) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/CustomField.php b/www/modules/civicrm/CRM/Core/DAO/CustomField.php index bdda36d55..e1dcd6f16 100644 --- a/www/modules/civicrm/CRM/Core/DAO/CustomField.php +++ b/www/modules/civicrm/CRM/Core/DAO/CustomField.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/CustomField.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:5272bbce1af37e0a1dd56fed8ca8ecdf) + * (GenCodeChecksum:c077ed8e18907eb52bece2c3b057bc8b) */ /** @@ -320,6 +320,15 @@ class CRM_Core_DAO_CustomField extends CRM_Core_DAO { */ public $fk_entity; + /** + * Behavior if referenced entity is deleted. + * + * @var string + * (SQL type: varchar(255)) + * Note that values will be retrieved from the database as a string. + */ + public $fk_entity_on_delete; + /** * Class constructor. */ @@ -982,6 +991,34 @@ public static function &fields() { 'localizable' => 0, 'add' => '5.60', ], + 'fk_entity_on_delete' => [ + 'name' => 'fk_entity_on_delete', + 'type' => CRM_Utils_Type::T_STRING, + 'title' => ts('On Referenced Entity Delete'), + 'description' => ts('Behavior if referenced entity is deleted.'), + 'required' => TRUE, + 'maxlength' => 255, + 'size' => CRM_Utils_Type::HUGE, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_custom_field.fk_entity_on_delete', + 'default' => 'set_null', + 'table_name' => 'civicrm_custom_field', + 'entity' => 'CustomField', + 'bao' => 'CRM_Core_BAO_CustomField', + 'localizable' => 0, + 'html' => [ + 'type' => 'Select', + ], + 'pseudoconstant' => [ + 'callback' => 'CRM_Core_BAO_CustomField::getFkEntityOnDeleteOptions', + ], + 'add' => '5.71', + ], ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } diff --git a/www/modules/civicrm/CRM/Core/DAO/CustomGroup.php b/www/modules/civicrm/CRM/Core/DAO/CustomGroup.php index 3f25213da..19df066ff 100644 --- a/www/modules/civicrm/CRM/Core/DAO/CustomGroup.php +++ b/www/modules/civicrm/CRM/Core/DAO/CustomGroup.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/CustomGroup.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:d87ef7faf84bd9ec8d1ac5195054a05a) + * (GenCodeChecksum:29f69d8dd04f79156872f073db98630f) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/Dashboard.php b/www/modules/civicrm/CRM/Core/DAO/Dashboard.php index 40b9857ea..f76960633 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Dashboard.php +++ b/www/modules/civicrm/CRM/Core/DAO/Dashboard.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Dashboard.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:1131a6e3957e7ce8e0a2371565562cad) + * (GenCodeChecksum:1794407b4bd25c2c73eb6ea55162e2b8) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/Discount.php b/www/modules/civicrm/CRM/Core/DAO/Discount.php index 826035221..66a461ad0 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Discount.php +++ b/www/modules/civicrm/CRM/Core/DAO/Discount.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Discount.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:937a5a030125d65e7b8a064cecf919fd) + * (GenCodeChecksum:3859fe3804f3150b107fa7a57c89b8b9) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/Domain.php b/www/modules/civicrm/CRM/Core/DAO/Domain.php index f83c19947..ba2ca9493 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Domain.php +++ b/www/modules/civicrm/CRM/Core/DAO/Domain.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Domain.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:4814b16c7b4c8d130e3e29d7ae309d88) + * (GenCodeChecksum:360ded3eb93ffebb5612b7ec6f5c2e06) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/Email.php b/www/modules/civicrm/CRM/Core/DAO/Email.php index 767c51f51..9c0eed6e1 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Email.php +++ b/www/modules/civicrm/CRM/Core/DAO/Email.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Email.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:840e0540daf99f6fad7bf13389157479) + * (GenCodeChecksum:c0c0fa9525e9381e7a8e51229addf48a) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/EntityFile.php b/www/modules/civicrm/CRM/Core/DAO/EntityFile.php index 889145562..b651379d5 100644 --- a/www/modules/civicrm/CRM/Core/DAO/EntityFile.php +++ b/www/modules/civicrm/CRM/Core/DAO/EntityFile.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/EntityFile.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:e2899940239f57513f7bc889b482907c) + * (GenCodeChecksum:936bff540e7e602f007e9ddeafff8b6c) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/EntityTag.php b/www/modules/civicrm/CRM/Core/DAO/EntityTag.php index b8f166bd8..07205684f 100644 --- a/www/modules/civicrm/CRM/Core/DAO/EntityTag.php +++ b/www/modules/civicrm/CRM/Core/DAO/EntityTag.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/EntityTag.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:0c6284da3e376f8bcc7e75f2382eea41) + * (GenCodeChecksum:a738d9be15f6cfafacd3e4a14cd30fa0) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/Extension.php b/www/modules/civicrm/CRM/Core/DAO/Extension.php index 7a43a939e..54546f5f2 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Extension.php +++ b/www/modules/civicrm/CRM/Core/DAO/Extension.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Extension.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:ed9e7b26e41992fa8f4e81e942eedf14) + * (GenCodeChecksum:330fdc407870ef3808daba1198b07e3b) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/File.php b/www/modules/civicrm/CRM/Core/DAO/File.php index 5f0677e85..f9398ba68 100644 --- a/www/modules/civicrm/CRM/Core/DAO/File.php +++ b/www/modules/civicrm/CRM/Core/DAO/File.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/File.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:3da10c3077bcd95ae97573351c54c377) + * (GenCodeChecksum:0f54a53356092fa11da07162c141e43f) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/IM.php b/www/modules/civicrm/CRM/Core/DAO/IM.php index b069791a6..5f696f3a0 100644 --- a/www/modules/civicrm/CRM/Core/DAO/IM.php +++ b/www/modules/civicrm/CRM/Core/DAO/IM.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/IM.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:55bc6457cdc907da89416f4904e1f8c4) + * (GenCodeChecksum:74c6cf2f3428d7727169664ebb0a719e) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/Job.php b/www/modules/civicrm/CRM/Core/DAO/Job.php index b663617f6..54c0d7322 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Job.php +++ b/www/modules/civicrm/CRM/Core/DAO/Job.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Job.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:fd14e515195028086bdf96c0fd3d8288) + * (GenCodeChecksum:caf43437176c75bc4bde4be837f3c17b) */ /** @@ -78,6 +78,15 @@ class CRM_Core_DAO_Job extends CRM_Core_DAO { */ public $last_run; + /** + * When did this cron entry last finish running + * + * @var string + * (SQL type: timestamp) + * Note that values will be retrieved from the database as a string. + */ + public $last_run_end; + /** * When is this cron entry scheduled to run * @@ -270,6 +279,29 @@ public static function &fields() { ], 'add' => '4.1', ], + 'last_run_end' => [ + 'name' => 'last_run_end', + 'type' => CRM_Utils_Type::T_TIMESTAMP, + 'title' => ts('Last Run End'), + 'description' => ts('When did this cron entry last finish running'), + 'required' => FALSE, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_job.last_run_end', + 'default' => NULL, + 'table_name' => 'civicrm_job', + 'entity' => 'Job', + 'bao' => 'CRM_Core_BAO_Job', + 'localizable' => 0, + 'html' => [ + 'label' => ts("Last Run End"), + ], + 'add' => '5.72', + ], 'scheduled_run_date' => [ 'name' => 'scheduled_run_date', 'type' => CRM_Utils_Type::T_TIMESTAMP, diff --git a/www/modules/civicrm/CRM/Core/DAO/JobLog.php b/www/modules/civicrm/CRM/Core/DAO/JobLog.php index 29344ddb7..17acc072e 100644 --- a/www/modules/civicrm/CRM/Core/DAO/JobLog.php +++ b/www/modules/civicrm/CRM/Core/DAO/JobLog.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/JobLog.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:2cc5d57c7214571368b304d67759dc29) + * (GenCodeChecksum:e762a6752acaf96ffbfbbb75c69fe2ea) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/LocBlock.php b/www/modules/civicrm/CRM/Core/DAO/LocBlock.php index c607c2f6b..aa411010b 100644 --- a/www/modules/civicrm/CRM/Core/DAO/LocBlock.php +++ b/www/modules/civicrm/CRM/Core/DAO/LocBlock.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/LocBlock.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:f5b7464778aa3355be9143c7bd44bef6) + * (GenCodeChecksum:d8b81ad2d5429a693c6681a08b36d2ab) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/LocationType.php b/www/modules/civicrm/CRM/Core/DAO/LocationType.php index f4810dd99..e1c790f0f 100644 --- a/www/modules/civicrm/CRM/Core/DAO/LocationType.php +++ b/www/modules/civicrm/CRM/Core/DAO/LocationType.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/LocationType.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:03307d8df09b1e1e9d99234f20faefc5) + * (GenCodeChecksum:f8a4d0de764c73095706b53af604c891) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/Log.php b/www/modules/civicrm/CRM/Core/DAO/Log.php index b9cef25ff..a64537a7f 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Log.php +++ b/www/modules/civicrm/CRM/Core/DAO/Log.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Log.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:a664e18ae03c664f20186590a3e5807c) + * (GenCodeChecksum:944dc25943521f310a45b3282fd55f7d) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/MailSettings.php b/www/modules/civicrm/CRM/Core/DAO/MailSettings.php index 3751f51b6..569b20dee 100644 --- a/www/modules/civicrm/CRM/Core/DAO/MailSettings.php +++ b/www/modules/civicrm/CRM/Core/DAO/MailSettings.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/MailSettings.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:02ee7569c4d85495c6fca92b53108695) + * (GenCodeChecksum:a8df3219199575e13ab5a977492ff9b5) */ /** @@ -187,6 +187,8 @@ class CRM_Core_DAO_MailSettings extends CRM_Core_DAO { public $is_non_case_email_skipped; /** + * If this option is enabled, CiviCRM will not create new contacts when filing emails. + * * @var bool|string * (SQL type: tinyint) * Note that values will be retrieved from the database as a string. @@ -629,6 +631,7 @@ public static function &fields() { 'name' => 'is_contact_creation_disabled_if_no_match', 'type' => CRM_Utils_Type::T_BOOLEAN, 'title' => ts('Do not create new contacts when filing emails'), + 'description' => ts('If this option is enabled, CiviCRM will not create new contacts when filing emails.'), 'required' => TRUE, 'usage' => [ 'import' => FALSE, @@ -722,7 +725,7 @@ public static function &fields() { 'table' => 'civicrm_campaign', 'keyColumn' => 'id', 'labelColumn' => 'title', - 'prefetch' => 'FALSE', + 'prefetch' => 'disabled', ], 'add' => '5.66', ], diff --git a/www/modules/civicrm/CRM/Core/DAO/Managed.php b/www/modules/civicrm/CRM/Core/DAO/Managed.php index 20eedc2c1..b95b2071a 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Managed.php +++ b/www/modules/civicrm/CRM/Core/DAO/Managed.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Managed.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:4ff1b19106d4bc7810d4aa9eb2a73f65) + * (GenCodeChecksum:b4a845b57085b6ac99d2b09bb21b72f8) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/Mapping.php b/www/modules/civicrm/CRM/Core/DAO/Mapping.php index 9a4ba1314..7dd6552d1 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Mapping.php +++ b/www/modules/civicrm/CRM/Core/DAO/Mapping.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Mapping.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:981063692534917d20a9c12fb65bbd8e) + * (GenCodeChecksum:bd700a2b140ebe3a72cd122bbe04efbd) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/MappingField.php b/www/modules/civicrm/CRM/Core/DAO/MappingField.php index a7b0f438c..8af462378 100644 --- a/www/modules/civicrm/CRM/Core/DAO/MappingField.php +++ b/www/modules/civicrm/CRM/Core/DAO/MappingField.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/MappingField.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:fde17b05b7e7ff25edec775c5a412f7e) + * (GenCodeChecksum:0eefb06ac4b267d77a8e592afb473158) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/Menu.php b/www/modules/civicrm/CRM/Core/DAO/Menu.php index 41eefa95b..e04550738 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Menu.php +++ b/www/modules/civicrm/CRM/Core/DAO/Menu.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Menu.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:68b2bb93878a4b972546d9e3f43e1a4c) + * (GenCodeChecksum:ee5ecb6683399d07b80816348c122245) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/MessageTemplate.php b/www/modules/civicrm/CRM/Core/DAO/MessageTemplate.php index a1a0143bd..06e97e586 100644 --- a/www/modules/civicrm/CRM/Core/DAO/MessageTemplate.php +++ b/www/modules/civicrm/CRM/Core/DAO/MessageTemplate.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/MessageTemplate.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:c5ea23571e85e6abb0e42fe204e97dc4) + * (GenCodeChecksum:4920aaedcde166a8a1d37318819c59a8) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/Navigation.php b/www/modules/civicrm/CRM/Core/DAO/Navigation.php index c2a28de8b..ee2f42cbe 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Navigation.php +++ b/www/modules/civicrm/CRM/Core/DAO/Navigation.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Navigation.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:23df3f63163a45fc78d97f9d41b40c59) + * (GenCodeChecksum:ec6750dd040ecd891bf2dc89d7ad6c1d) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/Note.php b/www/modules/civicrm/CRM/Core/DAO/Note.php index 1e16e4805..235ab459c 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Note.php +++ b/www/modules/civicrm/CRM/Core/DAO/Note.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Note.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:30875e48458bd58afb6a8098b58391d8) + * (GenCodeChecksum:834b6b69c06b1320b785a0735ceaf16a) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/OpenID.php b/www/modules/civicrm/CRM/Core/DAO/OpenID.php index 80f6c3b3e..6145862f8 100644 --- a/www/modules/civicrm/CRM/Core/DAO/OpenID.php +++ b/www/modules/civicrm/CRM/Core/DAO/OpenID.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/OpenID.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:ba943523844376fa804ca9fbbb63be8b) + * (GenCodeChecksum:31cf81d3b9757e11e4c69c26b3b5372c) */ /** @@ -206,6 +206,9 @@ public static function &fields() { 'entity' => 'OpenID', 'bao' => 'CRM_Core_BAO_OpenID', 'localizable' => 0, + 'html' => [ + 'type' => 'Url', + ], 'add' => '2.0', ], 'allowed_to_login' => [ diff --git a/www/modules/civicrm/CRM/Core/DAO/OptionGroup.php b/www/modules/civicrm/CRM/Core/DAO/OptionGroup.php index f77ad74a5..1f9df4c2a 100644 --- a/www/modules/civicrm/CRM/Core/DAO/OptionGroup.php +++ b/www/modules/civicrm/CRM/Core/DAO/OptionGroup.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/OptionGroup.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:264ddf7a9c772b66c6e472d8642fc9bb) + * (GenCodeChecksum:f4b6fc8c5d5536073aa4a4d643031af4) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/OptionValue.php b/www/modules/civicrm/CRM/Core/DAO/OptionValue.php index d775f9229..3a8a5a297 100644 --- a/www/modules/civicrm/CRM/Core/DAO/OptionValue.php +++ b/www/modules/civicrm/CRM/Core/DAO/OptionValue.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/OptionValue.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:631d0bd9404a447b384b44cc1b8daeff) + * (GenCodeChecksum:c2a476b386cc4eff6ff2e263b85b1baa) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/Phone.php b/www/modules/civicrm/CRM/Core/DAO/Phone.php index d9e2550e0..baa631c4e 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Phone.php +++ b/www/modules/civicrm/CRM/Core/DAO/Phone.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Phone.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:170cfc165e0c7d8b1cc7e5fc1696e49f) + * (GenCodeChecksum:eef1c7c8ec3a6b495c408d56e3919cd7) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/PreferencesDate.php b/www/modules/civicrm/CRM/Core/DAO/PreferencesDate.php index 02dddd0f3..5c6df7183 100644 --- a/www/modules/civicrm/CRM/Core/DAO/PreferencesDate.php +++ b/www/modules/civicrm/CRM/Core/DAO/PreferencesDate.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/PreferencesDate.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:7e0344ca89b265be75ff2995b711dc61) + * (GenCodeChecksum:e0f38c1447d53734a0aa77164b44f428) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/PrevNextCache.php b/www/modules/civicrm/CRM/Core/DAO/PrevNextCache.php index 83c9551a6..8e2bdbfa8 100644 --- a/www/modules/civicrm/CRM/Core/DAO/PrevNextCache.php +++ b/www/modules/civicrm/CRM/Core/DAO/PrevNextCache.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/PrevNextCache.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:2f3c2dc512edf7aabe12fdaeba209662) + * (GenCodeChecksum:9dd46541ee36c3a697ac172f781d80ef) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/PrintLabel.php b/www/modules/civicrm/CRM/Core/DAO/PrintLabel.php index 92e494674..020195cf1 100644 --- a/www/modules/civicrm/CRM/Core/DAO/PrintLabel.php +++ b/www/modules/civicrm/CRM/Core/DAO/PrintLabel.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/PrintLabel.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:666799f63e1b59a7fb459a736d37ebbb) + * (GenCodeChecksum:1124cab1af53aa83dd92863d92e2cb88) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/RecurringEntity.php b/www/modules/civicrm/CRM/Core/DAO/RecurringEntity.php index 67f113afe..5e82c6280 100644 --- a/www/modules/civicrm/CRM/Core/DAO/RecurringEntity.php +++ b/www/modules/civicrm/CRM/Core/DAO/RecurringEntity.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/RecurringEntity.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:5788a8f2b88586d6c98107e54253abd8) + * (GenCodeChecksum:b72cfc29284fa9f161f7dc9c42f3fffe) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/Setting.php b/www/modules/civicrm/CRM/Core/DAO/Setting.php index 056ac7387..a9bb2e245 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Setting.php +++ b/www/modules/civicrm/CRM/Core/DAO/Setting.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Setting.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:fd7511a97b39fb876ce599402a278428) + * (GenCodeChecksum:3bfd9f8a03acd2242706f1f7704d4115) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/StateProvince.php b/www/modules/civicrm/CRM/Core/DAO/StateProvince.php index 014cbe32f..243a484dd 100644 --- a/www/modules/civicrm/CRM/Core/DAO/StateProvince.php +++ b/www/modules/civicrm/CRM/Core/DAO/StateProvince.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/StateProvince.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:77d17a177f161cc2029169c9c6e9d2fa) + * (GenCodeChecksum:9397716a24018e701337006e9f534488) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/StatusPreference.php b/www/modules/civicrm/CRM/Core/DAO/StatusPreference.php index f440c79f4..998b7276f 100644 --- a/www/modules/civicrm/CRM/Core/DAO/StatusPreference.php +++ b/www/modules/civicrm/CRM/Core/DAO/StatusPreference.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/StatusPreference.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:f5a0658b23ddc17ca0b627791baa9b00) + * (GenCodeChecksum:72b79f9ff02ce8c6f2ff8db08ce4f82e) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/SystemLog.php b/www/modules/civicrm/CRM/Core/DAO/SystemLog.php index d0aeb1806..9ef95f8b3 100644 --- a/www/modules/civicrm/CRM/Core/DAO/SystemLog.php +++ b/www/modules/civicrm/CRM/Core/DAO/SystemLog.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/SystemLog.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:9439e784389ce4eb9d4b45366948ceb8) + * (GenCodeChecksum:b8a7c4c85422e5d1fa7bdeb8da9d91af) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/Tag.php b/www/modules/civicrm/CRM/Core/DAO/Tag.php index 76ffee608..08b873227 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Tag.php +++ b/www/modules/civicrm/CRM/Core/DAO/Tag.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Tag.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:3242cc59c5a6987043ff9b89fffd9812) + * (GenCodeChecksum:9589e91fed94b1480c7e282c7a49adab) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/Timezone.php b/www/modules/civicrm/CRM/Core/DAO/Timezone.php index 5849182f2..981bbd205 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Timezone.php +++ b/www/modules/civicrm/CRM/Core/DAO/Timezone.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Timezone.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:df6cc62c608df47d2b3d166d3393f45b) + * (GenCodeChecksum:163a497354efdcb836c4a91d398c167d) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/Translation.php b/www/modules/civicrm/CRM/Core/DAO/Translation.php index ec150c97c..a2d437480 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Translation.php +++ b/www/modules/civicrm/CRM/Core/DAO/Translation.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Translation.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:0e4a4c57a9282c3e3ddb08422400b36e) + * (GenCodeChecksum:b6f886b9df1bb1ef863269a1f3791fb7) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/UFField.php b/www/modules/civicrm/CRM/Core/DAO/UFField.php index b70e0ab1d..f0dc18bc7 100644 --- a/www/modules/civicrm/CRM/Core/DAO/UFField.php +++ b/www/modules/civicrm/CRM/Core/DAO/UFField.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/UFField.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:c5bb2d5c455892b58bd0ae998f8f8116) + * (GenCodeChecksum:45539bf36116ed34298bc82188fc820c) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/UFGroup.php b/www/modules/civicrm/CRM/Core/DAO/UFGroup.php index 0f744ab9a..134b8c500 100644 --- a/www/modules/civicrm/CRM/Core/DAO/UFGroup.php +++ b/www/modules/civicrm/CRM/Core/DAO/UFGroup.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/UFGroup.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:7e7f497a6e1fed5d867407723f1d4a33) + * (GenCodeChecksum:ebd219bcdff5f8bb34bb4107cdeecd8e) */ /** @@ -60,6 +60,15 @@ class CRM_Core_DAO_UFGroup extends CRM_Core_DAO { */ public $id; + /** + * Name of the UF group for directly addressing it in the codebase + * + * @var string + * (SQL type: varchar(64)) + * Note that values will be retrieved from the database as a string. + */ + public $name; + /** * Is this profile currently active? If false, hide all related fields for all sharing contexts. * @@ -90,7 +99,7 @@ class CRM_Core_DAO_UFGroup extends CRM_Core_DAO { /** * Profile Form Public title * - * @var string|null + * @var string * (SQL type: varchar(64)) * Note that values will be retrieved from the database as a string. */ @@ -229,15 +238,6 @@ class CRM_Core_DAO_UFGroup extends CRM_Core_DAO { */ public $is_reserved; - /** - * Name of the UF group for directly addressing it in the codebase - * - * @var string|null - * (SQL type: varchar(64)) - * Note that values will be retrieved from the database as a string. - */ - public $name; - /** * FK to civicrm_contact, who created this UF group * @@ -341,6 +341,30 @@ public static function &fields() { 'readonly' => TRUE, 'add' => '1.1', ], + 'name' => [ + 'name' => 'name', + 'type' => CRM_Utils_Type::T_STRING, + 'title' => ts('Profile Name'), + 'description' => ts('Name of the UF group for directly addressing it in the codebase'), + 'required' => TRUE, + 'maxlength' => 64, + 'size' => CRM_Utils_Type::BIG, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_uf_group.name', + 'table_name' => 'civicrm_uf_group', + 'entity' => 'UFGroup', + 'bao' => 'CRM_Core_BAO_UFGroup', + 'localizable' => 0, + 'html' => [ + 'type' => 'Text', + ], + 'add' => '3.0', + ], 'is_active' => [ 'name' => 'is_active', 'type' => CRM_Utils_Type::T_BOOLEAN, @@ -391,7 +415,7 @@ public static function &fields() { 'title' => [ 'name' => 'title', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Profile Name'), + 'title' => ts('Profile Title'), 'description' => ts('Form title.'), 'required' => TRUE, 'maxlength' => 64, @@ -417,6 +441,7 @@ public static function &fields() { 'type' => CRM_Utils_Type::T_STRING, 'title' => ts('Public Title'), 'description' => ts('Profile Form Public title'), + 'required' => TRUE, 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, 'usage' => [ @@ -757,26 +782,6 @@ public static function &fields() { ], 'add' => '3.0', ], - 'name' => [ - 'name' => 'name', - 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Profile Name'), - 'description' => ts('Name of the UF group for directly addressing it in the codebase'), - 'maxlength' => 64, - 'size' => CRM_Utils_Type::BIG, - 'usage' => [ - 'import' => FALSE, - 'export' => FALSE, - 'duplicate_matching' => FALSE, - 'token' => FALSE, - ], - 'where' => 'civicrm_uf_group.name', - 'table_name' => 'civicrm_uf_group', - 'entity' => 'UFGroup', - 'bao' => 'CRM_Core_BAO_UFGroup', - 'localizable' => 0, - 'add' => '3.0', - ], 'created_id' => [ 'name' => 'created_id', 'type' => CRM_Utils_Type::T_INT, diff --git a/www/modules/civicrm/CRM/Core/DAO/UFJoin.php b/www/modules/civicrm/CRM/Core/DAO/UFJoin.php index b2cd2efd5..76a687d86 100644 --- a/www/modules/civicrm/CRM/Core/DAO/UFJoin.php +++ b/www/modules/civicrm/CRM/Core/DAO/UFJoin.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/UFJoin.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:aec017870809d4d38daa4767c0ade45e) + * (GenCodeChecksum:fce6144a9b97377afc528bc5372e48df) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/UFMatch.php b/www/modules/civicrm/CRM/Core/DAO/UFMatch.php index df7f2ac81..2d50d7af8 100644 --- a/www/modules/civicrm/CRM/Core/DAO/UFMatch.php +++ b/www/modules/civicrm/CRM/Core/DAO/UFMatch.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/UFMatch.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:573747cc2ce1867945de4b241f7697c6) + * (GenCodeChecksum:25d315f3cc5d90e1400374e3b0919fa6) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/UserJob.php b/www/modules/civicrm/CRM/Core/DAO/UserJob.php index 100a4010b..47c130b8a 100644 --- a/www/modules/civicrm/CRM/Core/DAO/UserJob.php +++ b/www/modules/civicrm/CRM/Core/DAO/UserJob.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/UserJob.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:1dd5100f469beec27435f1444c73eeb5) + * (GenCodeChecksum:50b937f2d2cf21085dc68b549b58566d) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/Website.php b/www/modules/civicrm/CRM/Core/DAO/Website.php index 368e0de7e..95cfbc9af 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Website.php +++ b/www/modules/civicrm/CRM/Core/DAO/Website.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Website.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:0bc5336361de1d2286a64abc50719968) + * (GenCodeChecksum:04eef9c457f5f14e83b9eb002eacd0d5) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/WordReplacement.php b/www/modules/civicrm/CRM/Core/DAO/WordReplacement.php index 06f45883c..eef8bc817 100644 --- a/www/modules/civicrm/CRM/Core/DAO/WordReplacement.php +++ b/www/modules/civicrm/CRM/Core/DAO/WordReplacement.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/WordReplacement.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:f9d6a1b7c34275dac925cde23c8a02be) + * (GenCodeChecksum:dac83aac0d502f1703e85f35fc1ae623) */ /** diff --git a/www/modules/civicrm/CRM/Core/DAO/Worldregion.php b/www/modules/civicrm/CRM/Core/DAO/Worldregion.php index 75819d37a..e483648ae 100644 --- a/www/modules/civicrm/CRM/Core/DAO/Worldregion.php +++ b/www/modules/civicrm/CRM/Core/DAO/Worldregion.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/Worldregion.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:5a8bc073b156890e271f35afd867be85) + * (GenCodeChecksum:a9293175f5f503dcd9cfcec59fc967b5) */ /** diff --git a/www/modules/civicrm/CRM/Core/DynamicFKAccessTrait.php b/www/modules/civicrm/CRM/Core/DynamicFKAccessTrait.php index d2dbd72c6..5cd743a11 100644 --- a/www/modules/civicrm/CRM/Core/DynamicFKAccessTrait.php +++ b/www/modules/civicrm/CRM/Core/DynamicFKAccessTrait.php @@ -15,20 +15,21 @@ * @copyright CiviCRM LLC https://civicrm.org/licensing */ +use Civi\Api4\Event\AuthorizeRecordEvent; +use Civi\Api4\Utils\CoreUtil; + /** * Trait for with entities with an entity_table + entity_id dynamic FK. */ trait CRM_Core_DynamicFKAccessTrait { /** - * @param string $entityName - * @param string $action - * @param array $record - * @param int $userID - * @return bool * @see \Civi\Api4\Utils\CoreUtil::checkAccessRecord */ - public static function _checkAccess(string $entityName, string $action, array $record, int $userID): bool { + public static function self_civi_api4_authorizeRecord(AuthorizeRecordEvent $e): void { + $record = $e->getRecord(); + $userID = $e->getUserID(); + $delegateAction = $e->getActionName() === 'get' ? 'get' : 'update'; $eid = $record['entity_id'] ?? NULL; $table = $record['entity_table'] ?? NULL; if (!$eid && !empty($record['id'])) { @@ -43,9 +44,8 @@ public static function _checkAccess(string $entityName, string $action, array $r throw new \CRM_Core_Exception(sprintf('Cannot resolve permissions for dynamic foreign key in "%s". Invalid table reference "%s".', static::getTableName(), $table)); } - return \Civi\Api4\Utils\CoreUtil::checkAccessDelegated($targetEntity, 'update', ['id' => $eid], $userID); + $e->setAuthorized(CoreUtil::checkAccessDelegated($targetEntity, $delegateAction, ['id' => $eid], $userID)); } - return TRUE; } } diff --git a/www/modules/civicrm/CRM/Core/EntityTokens.php b/www/modules/civicrm/CRM/Core/EntityTokens.php index 88d5cc2de..74b5d6b37 100644 --- a/www/modules/civicrm/CRM/Core/EntityTokens.php +++ b/www/modules/civicrm/CRM/Core/EntityTokens.php @@ -17,6 +17,7 @@ use Civi\ActionSchedule\Event\MailingQueryEvent; use Civi\Token\TokenProcessor; use Brick\Money\Money; +use Brick\Math\RoundingMode; /** * Class CRM_Core_EntityTokens @@ -126,7 +127,7 @@ public function evaluateToken(TokenRow $row, $entity, $field, $prefetch = NULL) } return $row->format('text/plain')->tokens($entity, $field, - Money::of($fieldValue, $currency)); + Money::of($fieldValue, $currency, NULL, RoundingMode::HALF_UP)); } if ($this->isDateField($field)) { @@ -306,7 +307,7 @@ protected function getRelatedTokens(): array { * @internal function will likely be protected soon. */ protected function getPseudoValue(string $realField, string $pseudoKey, $fieldValue): string { - $bao = CRM_Core_DAO_AllCoreTables::getFullName($this->getMetadataForField($realField)['entity']); + $bao = CRM_Core_DAO_AllCoreTables::getDAONameForEntity($this->getMetadataForField($realField)['entity']); if ($pseudoKey === 'name') { // There is a theoretical possibility fieldValue could be an array but // specifically for preferred communication type - but real world usage diff --git a/www/modules/civicrm/CRM/Core/Error.php b/www/modules/civicrm/CRM/Core/Error.php index 28dac3a85..ee3e62fa4 100644 --- a/www/modules/civicrm/CRM/Core/Error.php +++ b/www/modules/civicrm/CRM/Core/Error.php @@ -182,7 +182,7 @@ public static function handle($pearError) { else { $mysql_error = 'fixme-unknown-db-cxn'; } - $template->assign_by_ref('mysql_code', $mysql_error); + $template->assign('mysql_code', $mysql_error); } } @@ -199,9 +199,9 @@ public static function handle($pearError) { } } - $template->assign_by_ref('error', $error); + $template->assign('error', $error); $errorDetails = CRM_Core_Error::debug('', $error, FALSE); - $template->assign_by_ref('errorDetails', $errorDetails); + $template->assign('errorDetails', $errorDetails); CRM_Core_Error::debug_var('Fatal Error Details', $error, TRUE, TRUE, '', PEAR_LOG_ERR); CRM_Core_Error::backtrace('backTrace', TRUE); @@ -451,6 +451,9 @@ function_exists($config->fatalErrorHandler) $content = self::formatHtmlException($exception) . $content; } + // set the response code before starting the request + http_response_code(500); + echo CRM_Utils_System::theme($content); $exit = CRM_Utils_System::shouldExitAfterFatal(); diff --git a/www/modules/civicrm/CRM/Core/Form.php b/www/modules/civicrm/CRM/Core/Form.php index efe2d064a..2269adb0e 100644 --- a/www/modules/civicrm/CRM/Core/Form.php +++ b/www/modules/civicrm/CRM/Core/Form.php @@ -201,6 +201,11 @@ class CRM_Core_Form extends HTML_QuickForm_Page { */ public $_tagsetInfo; + /** + * @var true + */ + private bool $isValidated = FALSE; + /** * @return string */ @@ -396,7 +401,7 @@ public function __construct( // Workaround for CRM-15153 - give each form a reasonably unique css class $this->addClass(CRM_Utils_System::getClassName($this)); - $this->assign('snippet', CRM_Utils_Array::value('snippet', $_GET)); + $this->assign('snippet', $_GET['snippet'] ?? NULL); $this->setTranslatedFields(); } @@ -664,6 +669,23 @@ public function postProcessHook() { CRM_Utils_Hook::postProcess(get_class($this), $this); } + /** + * Register a field with quick form as supporting a file upload. + * + * @param array $fieldNames + * + * @return void + */ + public function registerFileField(array $fieldNames): void { + // hack for field type File + $formUploadNames = $this->get('uploadNames'); + if (is_array($formUploadNames)) { + $fieldNames = array_unique(array_merge($formUploadNames, $fieldNames)); + } + + $this->set('uploadNames', $fieldNames); + } + /** * This virtual function is used to build the form. * @@ -717,7 +739,7 @@ public function validate() { if (!empty($hookErrors)) { $this->_errors += $hookErrors; } - + $this->isValidated = TRUE; return (0 == count($this->_errors)); } @@ -757,7 +779,7 @@ public function buildForm() { } if (!empty($defaults)) { - $this->setDefaults($defaults); + $this->setPurifiedDefaults($defaults); } // call the form hook @@ -788,6 +810,33 @@ public function buildForm() { $this->_formBuilt = TRUE; } + /** + * Override this in a subclass to prevent fields intended to contain + * "raw html" from getting broken. E.g. system message templates + * @return array + */ + protected function getFieldsToExcludeFromPurification(): array { + return []; + } + + public function setPurifiedDefaults($defaults) { + $exclude = $this->getFieldsToExcludeFromPurification(); + foreach ($defaults as $index => $default) { + if (!in_array($index, $exclude, TRUE) && is_string($default) && !is_numeric($default)) { + $hasEncodedAmp = str_contains('&', $default); + $hasEncodedQuote = str_contains('"', $default); + $defaults[$index] = CRM_Utils_String::purifyHTML($default); + if (!$hasEncodedAmp) { + $defaults[$index] = str_replace('&', '&', $defaults[$index]); + } + if (!$hasEncodedQuote) { + $defaults[$index] = str_replace('"', '"', $defaults[$index]); + } + } + } + $this->setDefaults($defaults); + } + /** * Add default Next / Back buttons. * @@ -851,7 +900,7 @@ public function addButtons($params) { $attrs['accesskey'] = 'S'; } $buttonContents = CRM_Core_Page::crmIcon($button['icon'] ?? $defaultIcon) . ' ' . $button['name']; - $buttonName = $this->getButtonName($button['type'], CRM_Utils_Array::value('subName', $button)); + $buttonName = $this->getButtonName($button['type'], $button['subName'] ?? NULL); $attrs['class'] .= " crm-button crm-button-type-{$button['type']} crm-button{$buttonName}"; $attrs['type'] = 'submit'; $prevnext[] = $this->createElement('xbutton', $buttonName, $buttonContents, $attrs); @@ -907,7 +956,7 @@ public function getStateType() { * @return string */ public function getTitle() { - return $this->_title ? $this->_title : ts('ERROR: Title is not Set'); + return $this->_title ?: ts('ERROR: Title is not Set'); } /** @@ -1374,9 +1423,12 @@ public function assign($var, $value = NULL) { * Name of variable. * @param mixed $value * Value of variable. + * + * @deprecated since 5.72 will be removed around 5.84 */ public function assign_by_ref($var, &$value) { - self::$_template->assign_by_ref($var, $value); + CRM_Core_Error::deprecatedFunctionWarning('assign'); + self::$_template->assign($var, $value); } /** @@ -2112,9 +2164,13 @@ public function addUploadElement($elementName) { */ public function getVar($name) { try { - if (!empty(ReflectionUtils::getCodeDocs((new ReflectionProperty($this, $name)), 'Property')['deprecated'])) { + $property = ReflectionUtils::getCodeDocs((new ReflectionProperty($this, $name)), 'Property'); + if (!empty($property['deprecated'])) { CRM_Core_Error::deprecatedWarning('deprecated property accessed :' . $name); } + if (!empty($property['internal'])) { + CRM_Core_Error::deprecatedWarning('internal property accessed (this property could change without warning):' . $name); + } } catch (\ReflectionException $e) { // If the variable isn't defined you can't access its properties to check if it's deprecated. Let php 8.2 deal with those warnings. @@ -2390,7 +2446,7 @@ public function addAutocomplete(string $name, string $label = '', array $props = */ public function addEntityRef($name, $label = '', $props = [], $required = FALSE) { // Default properties - $props['api'] = CRM_Utils_Array::value('api', $props, []); + $props['api'] = $props['api'] ?? []; $props['entity'] = CRM_Core_DAO_AllCoreTables::convertEntityNameToCamel($props['entity'] ?? 'Contact'); $props['class'] = ltrim(($props['class'] ?? '') . ' crm-form-entityref'); @@ -2404,7 +2460,7 @@ public function addEntityRef($name, $label = '', $props = [], $required = FALSE) if (!empty($props['multiple'])) { $defaults['multiple'] = TRUE; } - $props['select'] = CRM_Utils_Array::value('select', $props, []) + $defaults; + $props['select'] = ($props['select'] ?? []) + $defaults; $this->formatReferenceFieldAttributes($props, get_class($this)); return $this->add('text', $name, $label, $props, $required); @@ -3070,8 +3126,12 @@ protected function getContactIDIfAccessingOwnRecord() { * * These values have been validated against the fields added to the form. * https://pear.php.net/manual/en/package.html.html-quickform.html-quickform.exportvalues.php + * unless the function is being called during before the submission has + * been validated. In which the values are not yet validated & hence + * taking directly from $_POST. * - * Any money processing has also been done. + * Fields with money or number formats are converted from localised formats + * before returning. * * @param string $fieldName * @@ -3083,6 +3143,18 @@ protected function getContactIDIfAccessingOwnRecord() { */ public function getSubmittedValue(string $fieldName) { if (empty($this->exportedValues)) { + if (!$this->isValidated) { + // Trying to access the submitted value before during during validate. + // In this case we get the submitValue which is equivalent to the value + // in $_POST. By contrast exportValues will filter out fields + // that have not been added to QuickForm. + $value = $this->getSubmitValue($fieldName); + if (is_string($value)) { + // Precaution since we are dealing with values directly in $_POST. + $value = CRM_Utils_String::purifyHTML($value); + } + return $this->getUnLocalizedSubmittedValue($fieldName, $value); + } $this->exportedValues = $this->controller->exportValues($this->_name); } $value = $this->exportedValues[$fieldName] ?? NULL; diff --git a/www/modules/civicrm/CRM/Core/Form/EntityFormTrait.php b/www/modules/civicrm/CRM/Core/Form/EntityFormTrait.php index 3941da88d..f1bbb7deb 100644 --- a/www/modules/civicrm/CRM/Core/Form/EntityFormTrait.php +++ b/www/modules/civicrm/CRM/Core/Form/EntityFormTrait.php @@ -15,6 +15,7 @@ * @copyright CiviCRM LLC https://civicrm.org/licensing */ trait CRM_Core_Form_EntityFormTrait { + use CRM_Custom_Form_CustomDataTrait; /** * The id of the object being edited / created. @@ -156,9 +157,45 @@ public function addCustomDataToForm() { if ($this->isSuppressCustomData()) { return TRUE; } - $customisableEntities = CRM_Core_SelectValues::customGroupExtends(); - if (isset($customisableEntities[$this->getDefaultEntity()])) { - CRM_Custom_Form_CustomData::addToForm($this, $this->getEntitySubTypeId()); + + /* + @todo - this would be the preferred code here to better support + php8.2 & take advantage of code improvements + Note in this example an additional filter (membership_type_id) + is relevant although for most entities it isn't. + if ($this->isSubmitted()) { + // The custom data fields are added to the form by an ajax form. + // However, if they are not present in the element index they will + // not be available from `$this->getSubmittedValue()` in post process. + // We do not have to set defaults or otherwise render - just add to the element index. + $this->addCustomDataFieldsToForm($this->getDefaultEntity(), array_filter([ + 'id' => $this->getEntityId(), + 'membership_type_id' => $this->getSubmittedValue('membership_type_id') + ])); + } + */ + $this->assign('customDataType', $this->getDefaultEntity()); + $this->assign('customDataSubType', $this->getEntitySubTypeId()); + $this->assign('entityID', $this->getEntityId()); + $this->assign('cid', NULL); + if ($this->isSubmitted()) { + $customisableEntities = CRM_Core_SelectValues::customGroupExtends(); + if (isset($customisableEntities[$this->getDefaultEntity()])) { + if ($this->getEntitySubTypeId()) { + // Supporting entity subtypes form the EntityFormTrait is not + // used in core & is likely not used anywhere / was a good idea that + // didn't fully happen. If anyone is winding up here they should override + // the entire addCustomDataToForm function - e.g like the backoffice membership + // forms do + // @todo - add some noisy deprecation at some point. + CRM_Custom_Form_CustomData::addToForm($this, $this->getEntitySubTypeId()); + } + else { + $this->addCustomDataFieldsToForm($this->getDefaultEntity(), array_filter([ + 'id' => $this->getEntityId(), + ])); + } + } } } @@ -184,7 +221,7 @@ public function buildQuickEntityForm() { $this->assign('entityFields', $this->entityFields); $this->assign('entityID', $this->getEntityId()); $this->assign('entityInClassFormat', strtolower(str_replace('_', '-', $this->getDefaultEntity()))); - $this->assign('entityTable', CRM_Core_DAO_AllCoreTables::getTableForClass(CRM_Core_DAO_AllCoreTables::getFullName($this->getDefaultEntity()))); + $this->assign('entityTable', CRM_Core_DAO_AllCoreTables::getTableForClass(CRM_Core_DAO_AllCoreTables::getDAONameForEntity($this->getDefaultEntity()))); $this->addCustomDataToForm(); $this->addFormButtons(); diff --git a/www/modules/civicrm/CRM/Core/Form/Renderer.php b/www/modules/civicrm/CRM/Core/Form/Renderer.php index b1a589d30..01dc6bb0b 100644 --- a/www/modules/civicrm/CRM/Core/Form/Renderer.php +++ b/www/modules/civicrm/CRM/Core/Form/Renderer.php @@ -190,6 +190,9 @@ public static function updateAttributes(&$element, $required, $error) { elseif (strpos($class, 'crm-form-entityref') !== FALSE) { self::preProcessEntityRef($element); } + elseif (str_contains($class, 'crm-form-autocomplete')) { + self::preProcessAutocomplete($element); + } elseif (strpos($class, 'crm-form-contact-reference') !== FALSE) { self::preprocessContactReference($element); } @@ -233,7 +236,7 @@ public static function updateAttributes(&$element, $required, $error) { public function _tplFetch($tplSource) { // Smarty3 does not have this function defined so the parent fails. // Adding this is preparatory to smarty 3.... - if (!function_exists('smarty_function_eval') && !file_exists(SMARTY_DIR . '/plugins/function.eval.php')) { + if (!function_exists('smarty_function_eval') && (!defined('SMARTY_DIR') || !file_exists(SMARTY_DIR . '/plugins/function.eval.php'))) { $smarty = $this->_tpl; $smarty->assign('var', $tplSource); return $smarty->fetch("eval:$tplSource"); @@ -245,6 +248,16 @@ public function _tplFetch($tplSource) { return smarty_function_eval(['var' => $tplSource], $this->_tpl); } + /** + * @param HTML_QuickForm_element $field + */ + private static function preProcessAutocomplete($field) { + $val = $field->getValue(); + if (is_array($val)) { + $field->setValue(implode(',', $val)); + } + } + /** * Convert IDs to values and format for display. * diff --git a/www/modules/civicrm/CRM/Core/Form/Task.php b/www/modules/civicrm/CRM/Core/Form/Task.php index b4003bdd3..d9227c68b 100644 --- a/www/modules/civicrm/CRM/Core/Form/Task.php +++ b/www/modules/civicrm/CRM/Core/Form/Task.php @@ -391,7 +391,10 @@ protected function getRows(): array { * @return array */ protected function getContactIDs(): array { - return $this->_contactIds ?? []; + if (!isset($this->_contactIds)) { + $this->setContactIDs(); + } + return $this->_contactIds; } } diff --git a/www/modules/civicrm/CRM/Core/I18n.php b/www/modules/civicrm/CRM/Core/I18n.php index 2c789e152..e73d659c4 100644 --- a/www/modules/civicrm/CRM/Core/I18n.php +++ b/www/modules/civicrm/CRM/Core/I18n.php @@ -24,16 +24,13 @@ class CRM_Core_I18n { const NONE = 'none', AUTO = 'auto'; /** - * @var callable|null - * A callback function which handles SQL string encoding. - * Set NULL to use the default, CRM_Core_DAO::escapeString(). - * This is used by `ts(..., [escape=>sql])`. - * - * This option is not intended for general consumption. It is only intended - * for certain pre-boot/pre-install contexts. + * @var string + * @deprecated + * This variable has 1-2 references in contrib, which -probably- aren't functionally + * necessary. (Extensions don't load in pre-installation environments...) + * But we'll keep the property stub just to prevent crashes. * - * You might ask, "Why on Earth does string-translation have an opinion on - * SQL escaping?" Good question! + * Replaced by $GLOBALS['CIVICRM_SQL_ESCAPER']. */ public static $SQL_ESCAPER = NULL; @@ -47,12 +44,7 @@ class CRM_Core_I18n { 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); - } + return CRM_Core_DAO::escapeString($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); @@ -787,7 +779,7 @@ private function getWordReplacements() { } // FIXME: Is there a constant we can reference instead of hardcoding en_US? - $replacementsLocale = $this->locale ? $this->locale : 'en_US'; + $replacementsLocale = $this->locale ?: 'en_US'; if ((!isset(Civi::$statics[__CLASS__]) || !array_key_exists($replacementsLocale, Civi::$statics[__CLASS__]))) { if (defined('CIVICRM_DSN') && !CRM_Core_Config::isUpgradeMode()) { Civi::$statics[__CLASS__][$replacementsLocale] = CRM_Core_BAO_WordReplacement::getLocaleCustomStrings($replacementsLocale); @@ -800,71 +792,3 @@ private function getWordReplacements() { } } - -/** - * Short-named function for string translation, defined in global scope so it's available everywhere. - * - * @param string $text - * String for translating. - * Ex: 'Hello, %1!' - * @param array $params - * An array of additional parameters, as per `crm_translate()`. - * Ex: [1 => 'Dave'] - * @return string - * The translated string - * Ex: '¡Buenos días Dave!` - * @see \CRM_Core_I18n::crm_translate() - */ -function ts($text, $params = []) { - static $bootstrapReady = FALSE; - static $lastLocale = NULL; - static $i18n = NULL; - static $function = NULL; - - if ($text == '') { - return ''; - } - - // When the settings become available, lookup customTranslateFunction. - if (!$bootstrapReady) { - $bootstrapReady = (bool) \Civi\Core\Container::isContainerBooted(); - if ($bootstrapReady) { - // just got ready: determine whether there is a working custom translation function - $config = CRM_Core_Config::singleton(); - if (!empty($config->customTranslateFunction) && function_exists($config->customTranslateFunction)) { - $function = $config->customTranslateFunction; - } - } - } - - $civicrmLocale = CRM_Core_I18n::getLocale(); - if (!$i18n or $lastLocale != $civicrmLocale) { - $i18n = CRM_Core_I18n::singleton(); - $lastLocale = $civicrmLocale; - } - - if ($function) { - return $function($text, $params); - } - else { - return $i18n->crm_translate($text, $params); - } -} - -/** - * Alternate name for `ts()` - * - * This is functionally equivalent to `ts()`. However, regular `ts()` is subject to extra linting - * rules. Using `_ts()` can bypass the linting rules for the rare cases where you really want - * special/dynamic values. - * - * @param array ...$args - * @return string - * @see ts() - * @see \CRM_Core_I18n::crm_translate() - * @internal - */ -function _ts(...$args) { - $f = 'ts'; - return $f(...$args); -} diff --git a/www/modules/civicrm/CRM/Core/I18n/SchemaStructure.php b/www/modules/civicrm/CRM/Core/I18n/SchemaStructure.php index 672da8d72..f1a2ead7c 100644 --- a/www/modules/civicrm/CRM/Core/I18n/SchemaStructure.php +++ b/www/modules/civicrm/CRM/Core/I18n/SchemaStructure.php @@ -140,7 +140,7 @@ public static function &columns() { ], 'civicrm_uf_group' => [ 'title' => "varchar(64) NOT NULL DEFAULT '' COMMENT 'Form title.'", - 'frontend_title' => "varchar(64) COMMENT 'Profile Form Public title'", + 'frontend_title' => "varchar(64) NOT NULL DEFAULT '' COMMENT 'Profile Form Public title'", 'help_pre' => "text COMMENT 'Description and/or help text to display before fields in form.'", 'help_post' => "text COMMENT 'Description and/or help text to display after fields in form.'", 'cancel_button_text' => "varchar(64) DEFAULT NULL COMMENT 'Custom Text to display on the Cancel button when used in create or edit mode'", @@ -563,6 +563,7 @@ public static function &widgets() { ], 'frontend_title' => [ 'type' => "Text", + 'required' => "true", ], 'help_pre' => [ 'type' => "TextArea", @@ -691,7 +692,7 @@ public static function &widgets() { 'cols' => "50", ], 'confirm_email_text' => [ - 'type' => "TextArea", + 'type' => "RichTextEditor", 'rows' => "4", 'cols' => "50", ], diff --git a/www/modules/civicrm/CRM/Core/I18n/SchemaStructure_4_2_alpha1.php b/www/modules/civicrm/CRM/Core/I18n/SchemaStructure_4_2_alpha1.php deleted file mode 100644 index f74273b7f..000000000 --- a/www/modules/civicrm/CRM/Core/I18n/SchemaStructure_4_2_alpha1.php +++ /dev/null @@ -1,219 +0,0 @@ - [ - 'display_name' => "varchar(64)", - ], - 'civicrm_option_group' => [ - 'title' => "varchar(255)", - 'description' => "varchar(255)", - ], - 'civicrm_contact_type' => [ - 'label' => "varchar(64)", - 'description' => "text", - ], - 'civicrm_premiums' => [ - 'premiums_intro_title' => "varchar(255)", - 'premiums_intro_text' => "text", - ], - 'civicrm_product' => [ - 'name' => "varchar(255)", - 'description' => "text", - 'options' => "text", - ], - 'civicrm_membership_status' => [ - 'label' => "varchar(128)", - ], - 'civicrm_survey' => [ - 'thankyou_title' => "varchar(255)", - 'thankyou_text' => "text", - ], - 'civicrm_participant_status_type' => [ - 'label' => "varchar(255)", - ], - 'civicrm_tell_friend' => [ - 'title' => "varchar(255)", - 'intro' => "text", - 'suggested_message' => "text", - 'thankyou_title' => "varchar(255)", - 'thankyou_text' => "text", - ], - 'civicrm_price_set' => [ - 'title' => "varchar(255)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_batch' => [ - 'title' => "varchar(64)", - 'description' => "text", - ], - 'civicrm_custom_group' => [ - 'title' => "varchar(64)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_custom_field' => [ - 'label' => "varchar(255)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_dashboard' => [ - 'label' => "varchar(255)", - ], - 'civicrm_option_value' => [ - 'label' => "varchar(255)", - 'description' => "text", - ], - 'civicrm_group' => [ - 'title' => "varchar(64)", - ], - 'civicrm_contribution_page' => [ - 'title' => "varchar(255)", - 'intro_text' => "text", - 'pay_later_text' => "text", - 'pay_later_receipt' => "text", - 'thankyou_title' => "varchar(255)", - 'thankyou_text' => "text", - 'thankyou_footer' => "text", - 'for_organization' => "text", - 'receipt_from_name' => "varchar(255)", - 'receipt_text' => "text", - 'footer_text' => "text", - 'honor_block_title' => "varchar(255)", - 'honor_block_text' => "text", - ], - 'civicrm_price_field' => [ - 'label' => "varchar(255)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_uf_group' => [ - 'title' => "varchar(64)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_uf_field' => [ - 'help_post' => "text", - 'help_pre' => "text", - 'label' => "varchar(255)", - ], - 'civicrm_membership_type' => [ - 'name' => "varchar(128)", - 'description' => "varchar(255)", - ], - 'civicrm_membership_block' => [ - 'new_title' => "varchar(255)", - 'new_text' => "text", - 'renewal_title' => "varchar(255)", - 'renewal_text' => "text", - ], - 'civicrm_price_field_value' => [ - 'label' => "varchar(255)", - 'description' => "text", - ], - 'civicrm_pcp_block' => [ - 'link_text' => "varchar(255)", - ], - 'civicrm_event' => [ - 'title' => "varchar(255)", - 'summary' => "text", - 'description' => "text", - 'registration_link_text' => "varchar(255)", - 'event_full_text' => "text", - 'fee_label' => "varchar(255)", - 'intro_text' => "text", - 'footer_text' => "text", - 'confirm_title' => "varchar(255)", - 'confirm_text' => "text", - 'confirm_footer_text' => "text", - 'confirm_email_text' => "text", - 'confirm_from_name' => "varchar(255)", - 'thankyou_title' => "varchar(255)", - 'thankyou_text' => "text", - 'thankyou_footer_text' => "text", - 'pay_later_text' => "text", - 'pay_later_receipt' => "text", - 'waitlist_text' => "text", - 'approval_req_text' => "text", - 'template_title' => "varchar(255)", - ], - ]; - } - return $result; - } - - /** - * @return array - */ - public static function &indices() { - static $result = NULL; - if (!$result) { - $result = [ - 'civicrm_custom_group' => [ - 'UI_title_extends' => [ - 'name' => 'UI_title_extends', - 'field' => [ - 'title', - 'extends', - ], - 'unique' => 1, - ], - ], - 'civicrm_custom_field' => [ - 'UI_label_custom_group_id' => [ - 'name' => 'UI_label_custom_group_id', - 'field' => [ - 'label', - 'custom_group_id', - ], - 'unique' => 1, - ], - ], - 'civicrm_group' => [ - 'UI_title' => [ - 'name' => 'UI_title', - 'field' => [ - 'title', - ], - 'unique' => 1, - ], - ], - ]; - } - return $result; - } - - /** - * @return array - */ - public static function &tables() { - static $result = NULL; - if (!$result) { - $result = array_keys(self::columns()); - } - return $result; - } - -} diff --git a/www/modules/civicrm/CRM/Core/I18n/SchemaStructure_4_3_1.php b/www/modules/civicrm/CRM/Core/I18n/SchemaStructure_4_3_1.php deleted file mode 100644 index dd3d6dc12..000000000 --- a/www/modules/civicrm/CRM/Core/I18n/SchemaStructure_4_3_1.php +++ /dev/null @@ -1,224 +0,0 @@ - [ - 'display_name' => "varchar(64)", - ], - 'civicrm_option_group' => [ - 'title' => "varchar(255)", - 'description' => "varchar(255)", - ], - 'civicrm_contact_type' => [ - 'label' => "varchar(64)", - 'description' => "text", - ], - 'civicrm_batch' => [ - 'title' => "varchar(64)", - 'description' => "text", - ], - 'civicrm_premiums' => [ - 'premiums_intro_title' => "varchar(255)", - 'premiums_intro_text' => "text", - 'premiums_nothankyou_label' => "varchar(255)", - ], - 'civicrm_membership_status' => [ - 'label' => "varchar(128)", - ], - 'civicrm_survey' => [ - 'thankyou_title' => "varchar(255)", - 'thankyou_text' => "text", - ], - 'civicrm_participant_status_type' => [ - 'label' => "varchar(255)", - ], - 'civicrm_tell_friend' => [ - 'title' => "varchar(255)", - 'intro' => "text", - 'suggested_message' => "text", - 'thankyou_title' => "varchar(255)", - 'thankyou_text' => "text", - ], - 'civicrm_custom_group' => [ - 'title' => "varchar(64)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_custom_field' => [ - 'label' => "varchar(255)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_option_value' => [ - 'label' => "varchar(255)", - 'description' => "text", - ], - 'civicrm_group' => [ - 'title' => "varchar(64)", - ], - 'civicrm_contribution_page' => [ - 'title' => "varchar(255)", - 'intro_text' => "text", - 'pay_later_text' => "text", - 'pay_later_receipt' => "text", - 'initial_amount_label' => "varchar(255)", - 'initial_amount_help_text' => "text", - 'thankyou_title' => "varchar(255)", - 'thankyou_text' => "text", - 'thankyou_footer' => "text", - 'for_organization' => "text", - 'receipt_from_name' => "varchar(255)", - 'receipt_text' => "text", - 'footer_text' => "text", - 'honor_block_title' => "varchar(255)", - 'honor_block_text' => "text", - ], - 'civicrm_product' => [ - 'name' => "varchar(255)", - 'description' => "text", - 'options' => "text", - ], - 'civicrm_membership_type' => [ - 'name' => "varchar(128)", - 'description' => "varchar(255)", - ], - 'civicrm_membership_block' => [ - 'new_title' => "varchar(255)", - 'new_text' => "text", - 'renewal_title' => "varchar(255)", - 'renewal_text' => "text", - ], - 'civicrm_price_set' => [ - 'title' => "varchar(255)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_dashboard' => [ - 'label' => "varchar(255)", - ], - 'civicrm_uf_group' => [ - 'title' => "varchar(64)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_uf_field' => [ - 'help_post' => "text", - 'help_pre' => "text", - 'label' => "varchar(255)", - ], - 'civicrm_price_field' => [ - 'label' => "varchar(255)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_price_field_value' => [ - 'label' => "varchar(255)", - 'description' => "text", - ], - 'civicrm_pcp_block' => [ - 'link_text' => "varchar(255)", - ], - 'civicrm_event' => [ - 'title' => "varchar(255)", - 'summary' => "text", - 'description' => "text", - 'registration_link_text' => "varchar(255)", - 'event_full_text' => "text", - 'fee_label' => "varchar(255)", - 'intro_text' => "text", - 'footer_text' => "text", - 'confirm_title' => "varchar(255)", - 'confirm_text' => "text", - 'confirm_footer_text' => "text", - 'confirm_email_text' => "text", - 'confirm_from_name' => "varchar(255)", - 'thankyou_title' => "varchar(255)", - 'thankyou_text' => "text", - 'thankyou_footer_text' => "text", - 'pay_later_text' => "text", - 'pay_later_receipt' => "text", - 'initial_amount_label' => "varchar(255)", - 'initial_amount_help_text' => "text", - 'waitlist_text' => "text", - 'approval_req_text' => "text", - 'template_title' => "varchar(255)", - ], - ]; - } - return $result; - } - - /** - * @return array - */ - public static function &indices() { - static $result = NULL; - if (!$result) { - $result = [ - 'civicrm_custom_group' => [ - 'UI_title_extends' => [ - 'name' => 'UI_title_extends', - 'field' => [ - 'title', - 'extends', - ], - 'unique' => 1, - ], - ], - 'civicrm_custom_field' => [ - 'UI_label_custom_group_id' => [ - 'name' => 'UI_label_custom_group_id', - 'field' => [ - 'label', - 'custom_group_id', - ], - 'unique' => 1, - ], - ], - 'civicrm_group' => [ - 'UI_title' => [ - 'name' => 'UI_title', - 'field' => [ - 'title', - ], - 'unique' => 1, - ], - ], - ]; - } - return $result; - } - - /** - * @return array - */ - public static function &tables() { - static $result = NULL; - if (!$result) { - $result = array_keys(self::columns()); - } - return $result; - } - -} diff --git a/www/modules/civicrm/CRM/Core/I18n/SchemaStructure_4_5_alpha1.php b/www/modules/civicrm/CRM/Core/I18n/SchemaStructure_4_5_alpha1.php deleted file mode 100644 index 7aebd50d4..000000000 --- a/www/modules/civicrm/CRM/Core/I18n/SchemaStructure_4_5_alpha1.php +++ /dev/null @@ -1,227 +0,0 @@ - [ - 'display_name' => "varchar(64)", - ], - 'civicrm_option_group' => [ - 'title' => "varchar(255)", - 'description' => "varchar(255)", - ], - 'civicrm_contact_type' => [ - 'label' => "varchar(64)", - 'description' => "text", - ], - 'civicrm_batch' => [ - 'title' => "varchar(64)", - 'description' => "text", - ], - 'civicrm_premiums' => [ - 'premiums_intro_title' => "varchar(255)", - 'premiums_intro_text' => "text", - 'premiums_nothankyou_label' => "varchar(255)", - ], - 'civicrm_membership_status' => [ - 'label' => "varchar(128)", - ], - 'civicrm_survey' => [ - 'title' => "varchar(255)", - 'instructions' => "text", - 'thankyou_title' => "varchar(255)", - 'thankyou_text' => "text", - ], - 'civicrm_participant_status_type' => [ - 'label' => "varchar(255)", - ], - 'civicrm_tell_friend' => [ - 'title' => "varchar(255)", - 'intro' => "text", - 'suggested_message' => "text", - 'thankyou_title' => "varchar(255)", - 'thankyou_text' => "text", - ], - 'civicrm_custom_group' => [ - 'title' => "varchar(64)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_custom_field' => [ - 'label' => "varchar(255)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_option_value' => [ - 'label' => "varchar(255)", - 'description' => "text", - ], - 'civicrm_group' => [ - 'title' => "varchar(64)", - ], - 'civicrm_contribution_page' => [ - 'title' => "varchar(255)", - 'intro_text' => "text", - 'pay_later_text' => "text", - 'pay_later_receipt' => "text", - 'initial_amount_label' => "varchar(255)", - 'initial_amount_help_text' => "text", - 'thankyou_title' => "varchar(255)", - 'thankyou_text' => "text", - 'thankyou_footer' => "text", - 'for_organization' => "text", - 'receipt_from_name' => "varchar(255)", - 'receipt_text' => "text", - 'footer_text' => "text", - ], - 'civicrm_product' => [ - 'name' => "varchar(255)", - 'description' => "text", - 'options' => "text", - ], - 'civicrm_membership_type' => [ - 'name' => "varchar(128)", - 'description' => "varchar(255)", - ], - 'civicrm_membership_block' => [ - 'new_title' => "varchar(255)", - 'new_text' => "text", - 'renewal_title' => "varchar(255)", - 'renewal_text' => "text", - ], - 'civicrm_price_set' => [ - 'title' => "varchar(255)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_dashboard' => [ - 'label' => "varchar(255)", - ], - 'civicrm_uf_group' => [ - 'title' => "varchar(64)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_uf_field' => [ - 'help_post' => "text", - 'help_pre' => "text", - 'label' => "varchar(255)", - ], - 'civicrm_price_field' => [ - 'label' => "varchar(255)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_price_field_value' => [ - 'label' => "varchar(255)", - 'description' => "text", - ], - 'civicrm_pcp_block' => [ - 'link_text' => "varchar(255)", - ], - 'civicrm_event' => [ - 'title' => "varchar(255)", - 'summary' => "text", - 'description' => "text", - 'registration_link_text' => "varchar(255)", - 'event_full_text' => "text", - 'fee_label' => "varchar(255)", - 'intro_text' => "text", - 'footer_text' => "text", - 'confirm_title' => "varchar(255)", - 'confirm_text' => "text", - 'confirm_footer_text' => "text", - 'confirm_email_text' => "text", - 'confirm_from_name' => "varchar(255)", - 'thankyou_title' => "varchar(255)", - 'thankyou_text' => "text", - 'thankyou_footer_text' => "text", - 'pay_later_text' => "text", - 'pay_later_receipt' => "text", - 'initial_amount_label' => "varchar(255)", - 'initial_amount_help_text' => "text", - 'waitlist_text' => "text", - 'approval_req_text' => "text", - 'template_title' => "varchar(255)", - ], - ]; - } - return $result; - } - - /** - * @return array - */ - public static function &indices() { - static $result = NULL; - if (!$result) { - $result = [ - 'civicrm_custom_group' => [ - 'UI_title_extends' => [ - 'name' => 'UI_title_extends', - 'field' => [ - 'title', - 'extends', - ], - 'unique' => 1, - ], - ], - 'civicrm_custom_field' => [ - 'UI_label_custom_group_id' => [ - 'name' => 'UI_label_custom_group_id', - 'field' => [ - 'label', - 'custom_group_id', - ], - 'unique' => 1, - ], - ], - 'civicrm_group' => [ - 'UI_title' => [ - 'name' => 'UI_title', - 'field' => [ - 'title', - ], - 'unique' => 1, - ], - ], - ]; - } - return $result; - } - - /** - * @return array - */ - public static function &tables() { - static $result = NULL; - if (!$result) { - $result = array_keys(self::columns()); - } - return $result; - } - -} diff --git a/www/modules/civicrm/CRM/Core/I18n/SchemaStructure_4_5_beta2.php b/www/modules/civicrm/CRM/Core/I18n/SchemaStructure_4_5_beta2.php deleted file mode 100644 index 8558fddd5..000000000 --- a/www/modules/civicrm/CRM/Core/I18n/SchemaStructure_4_5_beta2.php +++ /dev/null @@ -1,235 +0,0 @@ - [ - 'display_name' => "varchar(64)", - ], - 'civicrm_option_group' => [ - 'title' => "varchar(255)", - 'description' => "varchar(255)", - ], - 'civicrm_contact_type' => [ - 'label' => "varchar(64)", - 'description' => "text", - ], - 'civicrm_batch' => [ - 'title' => "varchar(64)", - 'description' => "text", - ], - 'civicrm_premiums' => [ - 'premiums_intro_title' => "varchar(255)", - 'premiums_intro_text' => "text", - 'premiums_nothankyou_label' => "varchar(255)", - ], - 'civicrm_membership_status' => [ - 'label' => "varchar(128)", - ], - 'civicrm_survey' => [ - 'title' => "varchar(255)", - 'instructions' => "text", - 'thankyou_title' => "varchar(255)", - 'thankyou_text' => "text", - ], - 'civicrm_participant_status_type' => [ - 'label' => "varchar(255)", - ], - 'civicrm_case_type' => [ - 'title' => "varchar(64)", - 'description' => "varchar(255)", - ], - 'civicrm_tell_friend' => [ - 'title' => "varchar(255)", - 'intro' => "text", - 'suggested_message' => "text", - 'thankyou_title' => "varchar(255)", - 'thankyou_text' => "text", - ], - 'civicrm_custom_group' => [ - 'title' => "varchar(64)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_custom_field' => [ - 'label' => "varchar(255)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_option_value' => [ - 'label' => "varchar(255)", - 'description' => "text", - ], - 'civicrm_group' => [ - 'title' => "varchar(64)", - ], - 'civicrm_contribution_page' => [ - 'title' => "varchar(255)", - 'intro_text' => "text", - 'pay_later_text' => "text", - 'pay_later_receipt' => "text", - 'initial_amount_label' => "varchar(255)", - 'initial_amount_help_text' => "text", - 'thankyou_title' => "varchar(255)", - 'thankyou_text' => "text", - 'thankyou_footer' => "text", - 'for_organization' => "text", - 'receipt_from_name' => "varchar(255)", - 'receipt_text' => "text", - 'footer_text' => "text", - ], - 'civicrm_product' => [ - 'name' => "varchar(255)", - 'description' => "text", - 'options' => "text", - ], - 'civicrm_membership_type' => [ - 'name' => "varchar(128)", - 'description' => "varchar(255)", - ], - 'civicrm_membership_block' => [ - 'new_title' => "varchar(255)", - 'new_text' => "text", - 'renewal_title' => "varchar(255)", - 'renewal_text' => "text", - ], - 'civicrm_price_set' => [ - 'title' => "varchar(255)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_dashboard' => [ - 'label' => "varchar(255)", - ], - 'civicrm_uf_group' => [ - 'title' => "varchar(64)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_uf_field' => [ - 'help_post' => "text", - 'help_pre' => "text", - 'label' => "varchar(255)", - ], - 'civicrm_price_field' => [ - 'label' => "varchar(255)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_price_field_value' => [ - 'label' => "varchar(255)", - 'description' => "text", - ], - 'civicrm_pcp_block' => [ - 'link_text' => "varchar(255)", - ], - 'civicrm_event' => [ - 'title' => "varchar(255)", - 'summary' => "text", - 'description' => "text", - 'registration_link_text' => "varchar(255)", - 'event_full_text' => "text", - 'fee_label' => "varchar(255)", - 'intro_text' => "text", - 'footer_text' => "text", - 'confirm_title' => "varchar(255)", - 'confirm_text' => "text", - 'confirm_footer_text' => "text", - 'confirm_email_text' => "text", - 'confirm_from_name' => "varchar(255)", - 'thankyou_title' => "varchar(255)", - 'thankyou_text' => "text", - 'thankyou_footer_text' => "text", - 'pay_later_text' => "text", - 'pay_later_receipt' => "text", - 'initial_amount_label' => "varchar(255)", - 'initial_amount_help_text' => "text", - 'waitlist_text' => "text", - 'approval_req_text' => "text", - 'template_title' => "varchar(255)", - ], - ]; - } - return $result; - } - - /** - * Get indices. - * - * @return array - */ - public static function &indices() { - static $result = NULL; - if (!$result) { - $result = [ - 'civicrm_custom_group' => [ - 'UI_title_extends' => [ - 'name' => 'UI_title_extends', - 'field' => [ - 'title', - 'extends', - ], - 'unique' => 1, - ], - ], - 'civicrm_custom_field' => [ - 'UI_label_custom_group_id' => [ - 'name' => 'UI_label_custom_group_id', - 'field' => [ - 'label', - 'custom_group_id', - ], - 'unique' => 1, - ], - ], - 'civicrm_group' => [ - 'UI_title' => [ - 'name' => 'UI_title', - 'field' => [ - 'title', - ], - 'unique' => 1, - ], - ], - ]; - } - return $result; - } - - /** - * Get tables. - * - * @return array - */ - public static function &tables() { - static $result = NULL; - if (!$result) { - $result = array_keys(self::columns()); - } - return $result; - } - -} diff --git a/www/modules/civicrm/CRM/Core/I18n/SchemaStructure_4_7_alpha1.php b/www/modules/civicrm/CRM/Core/I18n/SchemaStructure_4_7_alpha1.php deleted file mode 100644 index dba47c734..000000000 --- a/www/modules/civicrm/CRM/Core/I18n/SchemaStructure_4_7_alpha1.php +++ /dev/null @@ -1,239 +0,0 @@ - [ - 'display_name' => "varchar(64)", - ], - 'civicrm_option_group' => [ - 'title' => "varchar(255)", - 'description' => "varchar(255)", - ], - 'civicrm_contact_type' => [ - 'label' => "varchar(64)", - 'description' => "text", - ], - 'civicrm_batch' => [ - 'title' => "varchar(64)", - 'description' => "text", - ], - 'civicrm_premiums' => [ - 'premiums_intro_title' => "varchar(255)", - 'premiums_intro_text' => "text", - 'premiums_nothankyou_label' => "varchar(255)", - ], - 'civicrm_membership_status' => [ - 'label' => "varchar(128)", - ], - 'civicrm_survey' => [ - 'title' => "varchar(255)", - 'instructions' => "text", - 'thankyou_title' => "varchar(255)", - 'thankyou_text' => "text", - ], - 'civicrm_participant_status_type' => [ - 'label' => "varchar(255)", - ], - 'civicrm_case_type' => [ - 'title' => "varchar(64)", - 'description' => "varchar(255)", - ], - 'civicrm_tell_friend' => [ - 'title' => "varchar(255)", - 'intro' => "text", - 'suggested_message' => "text", - 'thankyou_title' => "varchar(255)", - 'thankyou_text' => "text", - ], - 'civicrm_custom_group' => [ - 'title' => "varchar(64)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_custom_field' => [ - 'label' => "varchar(255)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_option_value' => [ - 'label' => "varchar(255)", - 'description' => "text", - ], - 'civicrm_group' => [ - 'title' => "varchar(64)", - ], - 'civicrm_contribution_page' => [ - 'title' => "varchar(255)", - 'intro_text' => "text", - 'pay_later_text' => "text", - 'pay_later_receipt' => "text", - 'initial_amount_label' => "varchar(255)", - 'initial_amount_help_text' => "text", - 'thankyou_title' => "varchar(255)", - 'thankyou_text' => "text", - 'thankyou_footer' => "text", - 'receipt_from_name' => "varchar(255)", - 'receipt_text' => "text", - 'footer_text' => "text", - ], - 'civicrm_product' => [ - 'name' => "varchar(255)", - 'description' => "text", - 'options' => "text", - ], - 'civicrm_membership_type' => [ - 'name' => "varchar(128)", - 'description' => "varchar(255)", - ], - 'civicrm_membership_block' => [ - 'new_title' => "varchar(255)", - 'new_text' => "text", - 'renewal_title' => "varchar(255)", - 'renewal_text' => "text", - ], - 'civicrm_price_set' => [ - 'title' => "varchar(255)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_dashboard' => [ - 'label' => "varchar(255)", - ], - 'civicrm_uf_group' => [ - 'title' => "varchar(64)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_uf_field' => [ - 'help_post' => "text", - 'help_pre' => "text", - 'label' => "varchar(255)", - ], - 'civicrm_price_field' => [ - 'label' => "varchar(255)", - 'help_pre' => "text", - 'help_post' => "text", - ], - 'civicrm_price_field_value' => [ - 'label' => "varchar(255)", - 'description' => "text", - ], - 'civicrm_pcp_block' => [ - 'link_text' => "varchar(255)", - ], - 'civicrm_event' => [ - 'title' => "varchar(255)", - 'summary' => "text", - 'description' => "text", - 'registration_link_text' => "varchar(255)", - 'event_full_text' => "text", - 'fee_label' => "varchar(255)", - 'intro_text' => "text", - 'footer_text' => "text", - 'confirm_title' => "varchar(255)", - 'confirm_text' => "text", - 'confirm_footer_text' => "text", - 'confirm_email_text' => "text", - 'confirm_from_name' => "varchar(255)", - 'thankyou_title' => "varchar(255)", - 'thankyou_text' => "text", - 'thankyou_footer_text' => "text", - 'pay_later_text' => "text", - 'pay_later_receipt' => "text", - 'initial_amount_label' => "varchar(255)", - 'initial_amount_help_text' => "text", - 'waitlist_text' => "text", - 'approval_req_text' => "text", - 'template_title' => "varchar(255)", - ], - 'civicrm_relationship_type' => [ - 'label_a_b' => "varchar(64)", - 'label_b_a' => "varchar(64)", - 'description' => "varchar(255)", - ], - ]; - } - return $result; - } - - /** - * Get indices. - * - * @return array - */ - public static function &indices() { - static $result = NULL; - if (!$result) { - $result = [ - 'civicrm_custom_group' => [ - 'UI_title_extends' => [ - 'name' => 'UI_title_extends', - 'field' => [ - 'title', - 'extends', - ], - 'unique' => 1, - ], - ], - 'civicrm_custom_field' => [ - 'UI_label_custom_group_id' => [ - 'name' => 'UI_label_custom_group_id', - 'field' => [ - 'label', - 'custom_group_id', - ], - 'unique' => 1, - ], - ], - 'civicrm_group' => [ - 'UI_title' => [ - 'name' => 'UI_title', - 'field' => [ - 'title', - ], - 'unique' => 1, - ], - ], - ]; - } - return $result; - } - - /** - * Get tables. - * - * @return array - */ - public static function &tables() { - static $result = NULL; - if (!$result) { - $result = array_keys(self::columns()); - } - return $result; - } - -} diff --git a/www/modules/civicrm/CRM/Core/IDS.php b/www/modules/civicrm/CRM/Core/IDS.php index ebdcda430..8c5cbd4cf 100644 --- a/www/modules/civicrm/CRM/Core/IDS.php +++ b/www/modules/civicrm/CRM/Core/IDS.php @@ -237,7 +237,7 @@ private function log($result, $reaction = 0) { $data = []; $session = CRM_Core_Session::singleton(); - $session_id = CRM_Core_Config::singleton()->userSystem->getSessionId() ? CRM_Core_Config::singleton()->userSystem->getSessionId() : '0'; + $session_id = CRM_Core_Config::singleton()->userSystem->getSessionId() ?: '0'; foreach ($result as $event) { $data[] = [ 'name' => $event->getName(), diff --git a/www/modules/civicrm/CRM/Core/Invoke.php b/www/modules/civicrm/CRM/Core/Invoke.php index b2a3e7555..2a1deac9b 100644 --- a/www/modules/civicrm/CRM/Core/Invoke.php +++ b/www/modules/civicrm/CRM/Core/Invoke.php @@ -120,15 +120,16 @@ public static function init($args) { $config = CRM_Core_Config::singleton(); // also initialize the i18n framework - require_once 'CRM/Core/I18n.php'; $i18n = CRM_Core_I18n::singleton(); } /** * Determine which menu $item corresponds to $args * - * @param array $args - * List of path parts. + * @param string|string[] $args + * Path to lookup + * Ex: 'civicrm/foo/bar' + * Ex: ['civicrm', 'foo', 'bar'] * @return array; see CRM_Core_Menu */ public static function getItem($args) { @@ -198,7 +199,9 @@ private static function registerPharHandler() { * * @param array $item * See CRM_Core_Menu. + * * @return string, HTML + * @throws \CRM_Core_Exception */ public static function runItem($item) { $ids = new CRM_Core_IDS(); @@ -229,6 +232,7 @@ public static function runItem($item) { // jsortable.tpl (datatables) $template->assign('sourceUrl'); $template->assign('useAjax', 0); + $template->assign('defaultOrderByDirection', 'asc'); if ($item) { @@ -314,7 +318,14 @@ public static function runItem($item) { $addSequence = $addSequence ? 'true' : 'false'; unset($pageArgs['addSequence']); } - $object = new $item['page_callback']($title, TRUE, $mode, NULL, $addSequence); + if ($item['page_callback'] === 'CRM_Import_Controller') { + // Let the generic import controller have the page arguments.... so we don't need + // one class per import. + $object = new CRM_Import_Controller($title, $pageArgs ?? []); + } + else { + $object = new $item['page_callback']($title, TRUE, $mode, NULL, $addSequence); + } } else { throw new CRM_Core_Exception('Execute supplied menu action'); diff --git a/www/modules/civicrm/CRM/Core/JobManager.php b/www/modules/civicrm/CRM/Core/JobManager.php index 28049bb8e..1b430c69d 100644 --- a/www/modules/civicrm/CRM/Core/JobManager.php +++ b/www/modules/civicrm/CRM/Core/JobManager.php @@ -32,8 +32,18 @@ class CRM_Core_JobManager { */ public $currentJob = NULL; + /** + * @var array + * + * @fixme How are these set? What do they do? + */ public $singleRunParams = []; + /** + * @var string|null + * + * @fixme Looks like this is only used by "singleRun" + */ public $_source = NULL; /** @@ -124,7 +134,7 @@ public function executeJob($job) { try { $result = civicrm_api($job->api_entity, $job->api_action, $params); } - catch (Exception$e) { + catch (Exception $e) { $this->logEntry('Error while executing ' . $job->name . ': ' . $e->getMessage()); $result = $e; } @@ -132,6 +142,9 @@ public function executeJob($job) { $this->logEntry('Finished execution of ' . $job->name . ' with result: ' . $this->apiResultToMessage($result)); $this->currentJob = FALSE; + // Save the job last run end date (if this doesn't get written we know the job crashed and was not caught (eg. OOM). + $job->saveLastRunEnd(); + //Disable outBound option after executing the job. $environment = CRM_Core_Config::environment(NULL, TRUE); if ($environment != 'Production' && !empty($job->apiParams['runInNonProductionEnvironment'])) { @@ -191,7 +204,7 @@ private function getJob($id = NULL, $entity = NULL, $action = NULL) { * @param $entity * @param $job * @param array $params - * @param null $source + * @param string|null $source */ public function setSingleRunParams($entity, $job, $params, $source = NULL) { $this->_source = $source; diff --git a/www/modules/civicrm/CRM/Core/Joomla.php b/www/modules/civicrm/CRM/Core/Joomla.php index 964cf72e8..706d8c46e 100644 --- a/www/modules/civicrm/CRM/Core/Joomla.php +++ b/www/modules/civicrm/CRM/Core/Joomla.php @@ -52,9 +52,9 @@ public static function sidebarLeft() { } $template = CRM_Core_Smarty::singleton(); - $template->assign_by_ref('blocks', $blocks); + $template->assign('blocks', $blocks); $sidebarLeft = $template->fetch('CRM/Block/blocks.tpl'); - $template->assign_by_ref('sidebarLeft', $sidebarLeft); + $template->assign('sidebarLeft', $sidebarLeft); } } diff --git a/www/modules/civicrm/CRM/Core/Page.php b/www/modules/civicrm/CRM/Core/Page.php index a9211bb52..254beee79 100644 --- a/www/modules/civicrm/CRM/Core/Page.php +++ b/www/modules/civicrm/CRM/Core/Page.php @@ -323,9 +323,12 @@ public function assign($var, $value = NULL) { * @param string $var * @param mixed $value * (reference) value of variable. + * + * @deprecated since 5.72 will be removed around 5.84 */ public function assign_by_ref($var, &$value) { - self::$_template->assign_by_ref($var, $value); + CRM_Core_Error::deprecatedFunctionWarning('assign'); + self::$_template->assign($var, $value); } /** diff --git a/www/modules/civicrm/CRM/Core/Payment.php b/www/modules/civicrm/CRM/Core/Payment.php index bc9a08b14..e824fffdf 100644 --- a/www/modules/civicrm/CRM/Core/Payment.php +++ b/www/modules/civicrm/CRM/Core/Payment.php @@ -9,6 +9,7 @@ +--------------------------------------------------------------------+ */ +use Civi\Api4\Participant; use Civi\Payment\System; use Civi\Payment\Exception\PaymentProcessorException; use Civi\Payment\PropertyBag; @@ -589,7 +590,13 @@ public function getText($context, $params) { return ''; case 'contributionPageContinueText': - return ts('Click the Continue button to proceed with the payment.'); + if ($params['amount'] <= 0.0 || (int) $this->_paymentProcessor['billing_mode'] === 4) { + return ts('Click the Continue button to proceed with the payment.'); + } + if ($params['is_payment_to_existing']) { + return ts('Click the Make Payment button to proceed with the payment.'); + } + return ts('Click the Make Contribution button to proceed with the payment.'); case 'contributionPageConfirmText': if ($params['amount'] <= 0.0) { @@ -1234,9 +1241,15 @@ public function getCancelUrl($qfKey, $participantID = NULL) { } if ($this->_component == 'event') { + $eventID = NULL; + if ($participantID) { + $eventID = Participant::get(FALSE)->addWhere('id', '=', $participantID) + ->addSelect('event_id')->execute()->single()['event_id']; + } return CRM_Utils_System::url($this->getBaseReturnUrl(), [ 'reset' => 1, 'cc' => 'fail', + 'id' => $eventID, 'participantId' => $participantID, ], TRUE, NULL, FALSE diff --git a/www/modules/civicrm/CRM/Core/Payment/Form.php b/www/modules/civicrm/CRM/Core/Payment/Form.php index bd91dc70c..e47ba6597 100644 --- a/www/modules/civicrm/CRM/Core/Payment/Form.php +++ b/www/modules/civicrm/CRM/Core/Payment/Form.php @@ -209,7 +209,7 @@ public static function getPaymentTypeLabel($paymentProcessor) { */ public static function buildPaymentForm(&$form, $processor, $billing_profile_id, $isBackOffice, $paymentInstrumentID = NULL) { //if the form has address fields assign to the template so the js can decide what billing fields to show - $form->assign('profileAddressFields', $form->get('profileAddressFields') ?? NULL); + $form->assign('profileAddressFields', $form->get('profileAddressFields')); $form->addExpectedSmartyVariable('suppressSubmitButton'); if (!empty($processor['object']) && $processor['object']->buildForm($form)) { return; @@ -313,7 +313,7 @@ public static function validateCreditCard($values, &$errors, $processorID = NULL /** * Map address fields. * - * @param int $id unused + * @param null $id unused * @param array $src * @param array $dst * @param bool $reverse diff --git a/www/modules/civicrm/CRM/Core/Payment/PayPalImpl.php b/www/modules/civicrm/CRM/Core/Payment/PayPalImpl.php index b9fb59828..f7db89690 100644 --- a/www/modules/civicrm/CRM/Core/Payment/PayPalImpl.php +++ b/www/modules/civicrm/CRM/Core/Payment/PayPalImpl.php @@ -1124,7 +1124,7 @@ public static function deformat($str) { $keyPos = strpos($str, '='); // position of value - $valPos = strpos($str, '&') ? strpos($str, '&') : strlen($str); + $valPos = strpos($str, '&') ?: strlen($str); /*getting the Key and Value values and storing in a Associative Array*/ diff --git a/www/modules/civicrm/CRM/Core/Payment/ProcessorForm.php b/www/modules/civicrm/CRM/Core/Payment/ProcessorForm.php index 257125e1d..43744d169 100644 --- a/www/modules/civicrm/CRM/Core/Payment/ProcessorForm.php +++ b/www/modules/civicrm/CRM/Core/Payment/ProcessorForm.php @@ -88,7 +88,7 @@ public static function preProcess($form) { $form->paymentInstrumentID ); - $form->assign_by_ref('paymentProcessor', $form->_paymentProcessor); + $form->assign('paymentProcessor', $form->_paymentProcessor); // check if this is a paypal auto return and redirect accordingly //@todo - determine if this is legacy and remove @@ -122,13 +122,13 @@ public static function preProcess($form) { /** * Build the payment processor form. * - * @param CRM_Core_Form $form + * @param \CRM_Event_Form_Registration_Register|\CRM_Contribute_Form_Contribution_Main|CRM_Event_Form_Registration_Confirm|CRM_Financial_Form_Payment $form */ - public static function buildQuickForm(&$form) { + public static function buildQuickForm($form): void { //@todo document why this addHidden is here //CRM-15743 - we should not set/create hidden element for pay later // because payment processor is not selected - $processorId = $form->getVar('_paymentProcessorID'); + $processorId = $form->getPaymentProcessorID(); $billing_profile_id = CRM_Utils_Request::retrieve('billing_profile_id', 'String'); if (!empty($form->_values) && !empty($form->_values['is_billing_required'])) { $billing_profile_id = 'billing'; diff --git a/www/modules/civicrm/CRM/Core/Permission.php b/www/modules/civicrm/CRM/Core/Permission.php index b37cf4c3e..cbc50c68b 100644 --- a/www/modules/civicrm/CRM/Core/Permission.php +++ b/www/modules/civicrm/CRM/Core/Permission.php @@ -128,8 +128,7 @@ public static function check($permissions, $contactId = NULL) { } else { // This is an individual permission - $impliedPermissions = self::getImpliedPermissionsFor($permission); - $impliedPermissions[] = $permission; + $impliedPermissions = self::getImpliedBy($permission); foreach ($impliedPermissions as $permissionOption) { $granted = CRM_Core_Config::singleton()->userPermissionClass->check($permissionOption, $userId); // Call the permission_check hook to permit dynamic escalation (CRM-19256) @@ -249,8 +248,9 @@ public static function customGroupAdmin($userId = NULL) { * @return int[] */ public static function customGroup($type = CRM_Core_Permission::VIEW, $reset = FALSE, $userId = NULL) { - $customGroups = CRM_Core_PseudoConstant::get('CRM_Core_DAO_CustomField', 'custom_group_id', - ['fresh' => $reset]); + $customGroups = CRM_Core_BAO_CustomGroup::getAll(); + // Hook expects a flat array of [id => name] + $customGroups = array_combine(array_keys($customGroups), array_column($customGroups, 'name')); // Administrators and users with 'access all custom data' can see all custom groups. if (self::customGroupAdmin($userId)) { @@ -563,41 +563,68 @@ public static function checkMenuItem(&$item) { } /** - * @param bool $all - * Include disabled components - * @param bool $descriptions - * Whether to return descriptions + * @param bool $includeDisabled + * Include permissions from disabled components/settings. + * @param bool $returnAssociative + * If true, returns arrays with keys: [label, description, disabled, implies, implied_by]. + * If false, returns strings (label only). * + * @return array[]|string[] + * @throws RuntimeException + */ + public static function basicPermissions($includeDisabled = FALSE, $returnAssociative = FALSE): array { + $permissions = Civi::$statics[__CLASS__][__FUNCTION__] ??= self::assembleBasicPermissions(); + if (!$includeDisabled) { + $permissions = array_filter($permissions, fn($permission) => empty($permission['disabled'])); + } + if ($returnAssociative) { + return $permissions; + } + return array_combine(array_keys($permissions), array_column($permissions, 'label')); + } + + /** * @return array + * @throws RuntimeException */ - public static function basicPermissions($all = FALSE, $descriptions = FALSE) { - $cacheKey = implode('-', [$all, $descriptions]); - if (empty(Civi::$statics[__CLASS__][__FUNCTION__][$cacheKey])) { - Civi::$statics[__CLASS__][__FUNCTION__][$cacheKey] = self::assembleBasicPermissions($all, $descriptions); + protected static function assembleBasicPermissions(): array { + $permissions = self::getCoreAndComponentPermissions(); + $module_permissions = CRM_Core_Config::singleton()->userPermissionClass->getAllModulePermissions(); + $allPermissions = array_merge($permissions, $module_permissions); + // Propagate implied_by permissions to their parents + foreach ($allPermissions as $name => $permission) { + foreach ($permission['implied_by'] ?? [] as $parent) { + if (isset($allPermissions[$parent])) { + $allPermissions[$parent]['implies'][] = $name; + } + } } - return Civi::$statics[__CLASS__][__FUNCTION__][$cacheKey]; + // Propagate implied permissions to their children + foreach ($allPermissions as $name => $permission) { + if (!empty($permission['implies'])) { + self::setImpliedBy([$name], $permission['implies'], $allPermissions); + } + } + return $allPermissions; } /** - * @param bool $all - * @param bool $descriptions - * whether to return descriptions + * Recursively sets the 'implied_by' value for every sub-permission, + * based on the 'implies' declaration in meta-permissions. * - * @return array - * @throws \CRM_Core_Exception + * @param array $metaPermissions + * @param array $subPermissions + * @param array $allPermissions */ - public static function assembleBasicPermissions($all = FALSE, $descriptions = FALSE): array { - $permissions = self::getCoreAndComponentPermissions($all); - - // Add any permissions defined in hook_civicrm_permission implementations. - $module_permissions = CRM_Core_Config::singleton()->userPermissionClass->getAllModulePermissions(TRUE); - $permissions = array_merge($permissions, $module_permissions); - if (!$descriptions) { - foreach ($permissions as $name => $attr) { - $permissions[$name] = array_shift($attr); + protected static function setImpliedBy(array $metaPermissions, array $subPermissions, array &$allPermissions): void { + foreach ($subPermissions as $name) { + if (isset($allPermissions[$name])) { + $allPermissions[$name]['implied_by'] = array_unique(array_merge($allPermissions[$name]['implied_by'] ?? [], $metaPermissions)); + if (!empty($allPermissions[$name]['implies'])) { + self::setImpliedBy(array_merge([$name], $metaPermissions), $allPermissions[$name]['implies'], $allPermissions); + } } } - return $permissions; } /** @@ -638,256 +665,306 @@ public static function getCorePermissions() { $prefix = ts('CiviCRM') . ': '; $permissions = [ 'add contacts' => [ - $prefix . ts('add contacts'), - ts('Create a new contact record in CiviCRM'), + 'label' => $prefix . ts('add contacts'), + 'description' => ts('Create a new contact record in CiviCRM'), ], 'view all contacts' => [ - $prefix . ts('view all contacts'), - ts('View ANY CONTACT in the CiviCRM database, export contact info and perform activities such as Send Email, Phone Call, etc.'), + 'label' => $prefix . ts('view all contacts'), + 'description' => ts('View ANY CONTACT in the CiviCRM database, export contact info and perform activities such as Send Email, Phone Call, etc.'), + 'implies' => [ + 'view my contact', + ], ], 'edit all contacts' => [ - $prefix . ts('edit all contacts'), - ts('View, Edit and Delete ANY CONTACT in the CiviCRM database; Create and edit relationships, tags and other info about the contacts'), + 'label' => $prefix . ts('edit all contacts'), + 'description' => ts('View, Edit and Delete ANY CONTACT in the CiviCRM database; Create and edit relationships, tags and other info about the contacts'), + 'implies' => [ + 'view all contacts', + 'edit my contact', + ], ], 'view my contact' => [ - $prefix . ts('view my contact'), + 'label' => $prefix . ts('view my contact'), ], 'edit my contact' => [ - $prefix . ts('edit my contact'), + 'label' => $prefix . ts('edit my contact'), ], 'delete contacts' => [ - $prefix . ts('delete contacts'), + 'label' => $prefix . ts('delete contacts'), ], 'access deleted contacts' => [ - $prefix . ts('access deleted contacts'), - ts('Access contacts in the trash'), + 'label' => $prefix . ts('access deleted contacts'), + 'description' => ts('Access contacts in the trash'), ], 'import contacts' => [ - $prefix . ts('import contacts'), - ts('Import contacts and activities'), + 'label' => $prefix . ts('import contacts'), + 'description' => ts('Import contacts and activities'), ], 'import SQL datasource' => [ - $prefix . ts('import SQL datasource'), - ts('When importing, consume data directly from a SQL datasource'), + 'label' => $prefix . ts('import SQL datasource'), + 'description' => ts('When importing, consume data directly from a SQL datasource'), ], 'edit groups' => [ - $prefix . ts('edit groups'), - ts('Create new groups, edit group settings (e.g. group name, visibility...), delete groups'), + 'label' => $prefix . ts('edit groups'), + 'description' => ts('Create new groups, edit group settings (e.g. group name, visibility...), delete groups'), ], 'administer CiviCRM' => [ - $prefix . ts('administer CiviCRM'), - ts('Perform all tasks in the Administer CiviCRM control panel and Import Contacts'), + 'label' => $prefix . ts('administer CiviCRM'), + 'description' => ts('Perform all tasks in the Administer CiviCRM control panel and Import Contacts'), + 'implies' => [ + 'administer CiviCRM system', + 'administer CiviCRM data', + 'access CiviCRM', + ], ], 'skip IDS check' => [ - $prefix . ts('skip IDS check'), - ts('Warning: Give to trusted roles only; this permission has security implications. IDS system is bypassed for users with this permission. Prevents false errors for admin users.'), + 'label' => $prefix . ts('skip IDS check'), + 'description' => ts('Warning: Give to trusted roles only; this permission has security implications. IDS system is bypassed for users with this permission. Prevents false errors for admin users.'), ], 'access uploaded files' => [ - $prefix . ts('access uploaded files'), - ts('View / download files including images and photos'), + 'label' => $prefix . ts('access uploaded files'), + 'description' => ts('View / download files including images and photos'), ], 'profile listings and forms' => [ - $prefix . ts('profile listings and forms'), - ts('Warning: Give to trusted roles only; this permission has privacy implications. Add/edit data in online forms and access public searchable directories.'), + 'label' => $prefix . ts('profile listings and forms'), + 'description' => ts('Warning: Give to trusted roles only; this permission has privacy implications. Add/edit data in online forms and access public searchable directories.'), + 'implies' => [ + 'profile listings', + ], ], 'profile listings' => [ - $prefix . ts('profile listings'), - ts('Warning: Give to trusted roles only; this permission has privacy implications. Access public searchable directories.'), + 'label' => $prefix . ts('profile listings'), + 'description' => ts('Warning: Give to trusted roles only; this permission has privacy implications. Access public searchable directories.'), ], 'profile create' => [ - $prefix . ts('profile create'), - ts('Add data in a profile form.'), + 'label' => $prefix . ts('profile create'), + 'description' => ts('Add data in a profile form.'), ], 'profile edit' => [ - $prefix . ts('profile edit'), - ts('Edit data in a profile form.'), + 'label' => $prefix . ts('profile edit'), + 'description' => ts('Edit data in a profile form.'), ], 'profile view' => [ - $prefix . ts('profile view'), - ts('View data in a profile.'), + 'label' => $prefix . ts('profile view'), + 'description' => ts('View data in a profile.'), ], 'access all custom data' => [ - $prefix . ts('access all custom data'), - ts('View all custom fields regardless of ACL rules'), + 'label' => $prefix . ts('access all custom data'), + 'description' => ts('View all custom fields regardless of ACL rules'), ], 'view all activities' => [ - $prefix . ts('view all activities'), - ts('View all activities (for visible contacts)'), + 'label' => $prefix . ts('view all activities'), + 'description' => ts('View all activities (for visible contacts)'), ], 'delete activities' => [ - $prefix . ts('Delete activities'), + 'label' => $prefix . ts('Delete activities'), ], 'edit inbound email basic information' => [ - $prefix . ts('edit inbound email basic information'), - ts('Edit all inbound email activities (for visible contacts) basic information. Content editing not allowed.'), + 'label' => $prefix . ts('edit inbound email basic information'), + 'description' => ts('Edit all inbound email activities (for visible contacts) basic information. Content editing not allowed.'), ], 'edit inbound email basic information and content' => [ - $prefix . ts('edit inbound email basic information and content'), - ts('Edit all inbound email activities (for visible contacts) basic information and content.'), + 'label' => $prefix . ts('edit inbound email basic information and content'), + 'description' => ts('Edit all inbound email activities (for visible contacts) basic information and content.'), ], 'access CiviCRM' => [ - $prefix . ts('access CiviCRM backend and API'), - ts('Master control for access to the main CiviCRM backend and API. Give to trusted roles only.'), + 'label' => $prefix . ts('access CiviCRM backend and API'), + 'description' => ts('Master control for access to the main CiviCRM backend and API. Give to trusted roles only.'), ], 'access Contact Dashboard' => [ - $prefix . ts('access Contact Dashboard'), - ts('View Contact Dashboard (for themselves and visible contacts)'), + 'label' => $prefix . ts('access Contact Dashboard'), + 'description' => ts('View Contact Dashboard (for themselves and visible contacts)'), ], 'translate CiviCRM' => [ - $prefix . ts('translate CiviCRM'), - ts('Allow User to enable multilingual'), + 'label' => $prefix . ts('translate CiviCRM'), + 'description' => ts('Allow User to enable multilingual'), ], 'manage tags' => [ - $prefix . ts('manage tags'), - ts('Create and rename tags'), + 'label' => $prefix . ts('manage tags'), + 'description' => ts('Create and rename tags'), ], 'administer reserved groups' => [ - $prefix . ts('administer reserved groups'), - ts('Edit and disable Reserved Groups (Needs Edit Groups)'), + 'label' => $prefix . ts('administer reserved groups'), + 'description' => ts('Edit and disable Reserved Groups (Needs Edit Groups)'), ], 'administer Tagsets' => [ - $prefix . ts('administer Tagsets'), + 'label' => $prefix . ts('administer Tagsets'), ], 'administer reserved tags' => [ - $prefix . ts('administer reserved tags'), + 'label' => $prefix . ts('administer reserved tags'), ], 'administer queues' => [ - $prefix . ts('administer queues'), - ts('Initialize, browse, and cancel background processing queues'), + 'label' => $prefix . ts('administer queues'), + 'description' => ts('Initialize, browse, and cancel background processing queues'), // At time of writing, we have specifically omitted the ability to edit fine-grained // data about specific queue-tasks. Tasks are usually defined as PHP callables... // and one should hesitate before allowing open-ended edits of PHP callables. // However, it seems fine for web-admins to browse and cancel these things. ], 'administer dedupe rules' => [ - $prefix . ts('administer dedupe rules'), - ts('Create and edit rules, change the supervised and unsupervised rules'), + 'label' => $prefix . ts('administer dedupe rules'), + 'description' => ts('Create and edit rules, change the supervised and unsupervised rules'), ], 'merge duplicate contacts' => [ - $prefix . ts('merge duplicate contacts'), - ts('Delete Contacts must also be granted in order for this to work.'), + 'label' => $prefix . ts('merge duplicate contacts'), + 'description' => ts('Delete Contacts must also be granted in order for this to work.'), ], 'force merge duplicate contacts' => [ - $prefix . ts('force merge duplicate contacts'), - ts('Delete Contacts must also be granted in order for this to work.'), + 'label' => $prefix . ts('force merge duplicate contacts'), + 'description' => ts('Delete Contacts must also be granted in order for this to work.'), ], 'view debug output' => [ - $prefix . ts('view debug output'), - ts('View results of debug and backtrace'), + 'label' => $prefix . ts('view debug output'), + 'description' => ts('View results of debug and backtrace'), ], 'view all notes' => [ - $prefix . ts('view all notes'), - ts("View notes (for visible contacts) even if they're marked author only"), + 'label' => $prefix . ts('view all notes'), + 'description' => ts("View notes (for visible contacts) even if they're marked author only"), ], 'add contact notes' => [ - $prefix . ts('add contact notes'), - ts("Create notes for contacts"), + 'label' => $prefix . ts('add contact notes'), + 'description' => ts("Create notes for contacts"), ], 'access AJAX API' => [ - $prefix . ts('access AJAX API'), - ts('Allow API access even if Access CiviCRM is not granted'), + 'label' => $prefix . ts('access AJAX API'), + 'description' => ts('Allow API access even if Access CiviCRM is not granted'), ], 'access contact reference fields' => [ - $prefix . ts('access contact reference fields'), - ts('Allow entering data into contact reference fields'), + 'label' => $prefix . ts('access contact reference fields'), + 'description' => ts('Allow entering data into contact reference fields'), ], 'create manual batch' => [ - $prefix . ts('create manual batch'), - ts('Create an accounting batch (with Access to CiviContribute and View Own/All Manual Batches)'), + 'label' => $prefix . ts('create manual batch'), + 'description' => ts('Create an accounting batch (with Access to CiviContribute and View Own/All Manual Batches)'), ], 'edit own manual batches' => [ - $prefix . ts('edit own manual batches'), - ts('Edit accounting batches created by user'), + 'label' => $prefix . ts('edit own manual batches'), + 'description' => ts('Edit accounting batches created by user'), ], 'edit all manual batches' => [ - $prefix . ts('edit all manual batches'), - ts('Edit all accounting batches'), + 'label' => $prefix . ts('edit all manual batches'), + 'description' => ts('Edit all accounting batches'), + 'implies' => [ + 'view all manual batches', + 'edit own manual batches', + ], ], 'close own manual batches' => [ - $prefix . ts('close own manual batches'), - ts('Close accounting batches created by user (with Access to CiviContribute)'), + 'label' => $prefix . ts('close own manual batches'), + 'description' => ts('Close accounting batches created by user (with Access to CiviContribute)'), ], 'close all manual batches' => [ - $prefix . ts('close all manual batches'), - ts('Close all accounting batches (with Access to CiviContribute)'), + 'label' => $prefix . ts('close all manual batches'), + 'description' => ts('Close all accounting batches (with Access to CiviContribute)'), + 'implies' => [ + 'close own manual batches', + ], ], 'reopen own manual batches' => [ - $prefix . ts('reopen own manual batches'), - ts('Reopen accounting batches created by user (with Access to CiviContribute)'), + 'label' => $prefix . ts('reopen own manual batches'), + 'description' => ts('Reopen accounting batches created by user (with Access to CiviContribute)'), ], 'reopen all manual batches' => [ - $prefix . ts('reopen all manual batches'), - ts('Reopen all accounting batches (with Access to CiviContribute)'), + 'label' => $prefix . ts('reopen all manual batches'), + 'description' => ts('Reopen all accounting batches (with Access to CiviContribute)'), + 'implies' => [ + 'reopen own manual batches', + ], ], 'view own manual batches' => [ - $prefix . ts('view own manual batches'), - ts('View accounting batches created by user (with Access to CiviContribute)'), + 'label' => $prefix . ts('view own manual batches'), + 'description' => ts('View accounting batches created by user (with Access to CiviContribute)'), ], 'view all manual batches' => [ - $prefix . ts('view all manual batches'), - ts('View all accounting batches (with Access to CiviContribute)'), + 'label' => $prefix . ts('view all manual batches'), + 'description' => ts('View all accounting batches (with Access to CiviContribute)'), + 'implies' => [ + 'view own manual batches', + ], ], 'delete own manual batches' => [ - $prefix . ts('delete own manual batches'), - ts('Delete accounting batches created by user'), + 'label' => $prefix . ts('delete own manual batches'), + 'description' => ts('Delete accounting batches created by user'), ], 'delete all manual batches' => [ - $prefix . ts('delete all manual batches'), - ts('Delete all accounting batches'), + 'label' => $prefix . ts('delete all manual batches'), + 'description' => ts('Delete all accounting batches'), + 'implies' => [ + 'delete own manual batches', + ], ], 'export own manual batches' => [ - $prefix . ts('export own manual batches'), - ts('Export accounting batches created by user'), + 'label' => $prefix . ts('export own manual batches'), + 'description' => ts('Export accounting batches created by user'), ], 'export all manual batches' => [ - $prefix . ts('export all manual batches'), - ts('Export all accounting batches'), + 'label' => $prefix . ts('export all manual batches'), + 'description' => ts('Export all accounting batches'), + 'implies' => [ + 'export own manual batches', + ], ], 'administer payment processors' => [ - $prefix . ts('administer payment processors'), - ts('Add, Update, or Disable Payment Processors'), + 'label' => $prefix . ts('administer payment processors'), + 'description' => ts('Add, Update, or Disable Payment Processors'), ], 'render templates' => [ - $prefix . ts('render templates'), - ts('Render open-ended template content. (Additional constraints may apply to autoloaded records and specific notations.)'), + 'label' => $prefix . ts('render templates'), + 'description' => ts('Render open-ended template content. (Additional constraints may apply to autoloaded records and specific notations.)'), ], 'edit message templates' => [ - $prefix . ts('edit message templates'), + 'label' => $prefix . ts('edit message templates'), ], 'edit system workflow message templates' => [ - $prefix . ts('edit system workflow message templates'), + 'label' => $prefix . ts('edit system workflow message templates'), ], 'edit user-driven message templates' => [ - $prefix . ts('edit user-driven message templates'), + 'label' => $prefix . ts('edit user-driven message templates'), ], 'view my invoices' => [ - $prefix . ts('view my invoices'), - ts('Allow users to view/ download their own invoices'), + 'label' => $prefix . ts('view my invoices'), + 'description' => ts('Allow users to view/ download their own invoices'), ], 'edit api keys' => [ - $prefix . ts('edit api keys'), - ts('Edit API keys'), + 'label' => $prefix . ts('edit api keys'), + 'description' => ts('Edit API keys'), + 'implies' => [ + 'edit own api keys', + ], ], 'edit own api keys' => [ - $prefix . ts('edit own api keys'), - ts('Edit user\'s own API keys'), + 'label' => $prefix . ts('edit own api keys'), + 'description' => ts("Edit user's own API keys"), ], 'send SMS' => [ - $prefix . ts('send SMS'), - ts('Send an SMS'), + 'label' => $prefix . ts('send SMS'), + 'description' => ts('Send an SMS'), ], 'administer CiviCRM system' => [ 'label' => $prefix . ts('administer CiviCRM System'), 'description' => ts('Perform all system administration tasks in CiviCRM'), + 'implies' => [ + 'edit system workflow message templates', + ], ], 'administer CiviCRM data' => [ 'label' => $prefix . ts('administer CiviCRM Data'), 'description' => ts('Permit altering all restricted data options'), + 'implies' => [ + 'edit message templates', + 'administer dedupe rules', + ], ], + // This is a very special permission that supersedes all others; + // it's the equivalent of user 1 in Drupal. 'all CiviCRM permissions and ACLs' => [ 'label' => $prefix . ts('all CiviCRM permissions and ACLs'), 'description' => ts('Administer and use CiviCRM bypassing any other permission or ACL checks and enabling the creation of displays and forms that allow others to bypass checks. This permission should be given out with care'), + // This line is here more as a bit of documentation (so it will show in `Civi\Api4\Permission::get()`). + // The functionality that actually propagates this permission into all others + // is in `self::getImpliedBy`. + 'implies' => ['*'], ], ]; if (self::isMultisiteEnabled()) { @@ -902,43 +979,37 @@ public static function getCorePermissions() { } /** - * Get permissions implied by 'superset' permissions. + * Get all permissions that would grant the given permission. * - * @return array - */ - public static function getImpliedAdminPermissions(): array { - return [ - 'administer CiviCRM' => ['implied_permissions' => ['administer CiviCRM system', 'administer CiviCRM data']], - 'administer CiviCRM data' => ['implied_permissions' => ['edit message templates', 'administer dedupe rules']], - 'administer CiviCRM system' => ['implied_permissions' => ['edit system workflow message templates']], - ]; - } - - /** - * Get any super-permissions that imply the given permission. - * - * @param string $permission + * This always includes the permission itself and the super 'all CiviCRM permissions and ACLs' + * plus any meta-permissions that imply this one. * + * @param string $permissionName * @return array */ - public static function getImpliedPermissionsFor(string $permission): array { - if (in_array($permission[0], ['@', '*'], TRUE)) { + private static function getImpliedBy(string $permissionName): array { + if (in_array($permissionName[0], ['@', '*'], TRUE)) { // Special permissions like '*always deny*' - see DynamicFKAuthorizationTest. // Also '@afform - see AfformUsageTest. - return []; - } - $implied = Civi::cache('metadata')->get('implied_permissions', []); - if (isset($implied[$permission])) { - return $implied[$permission]; + return [$permissionName]; } - $implied[$permission] = ['all CiviCRM permissions and ACLs']; - foreach (self::getImpliedAdminPermissions() as $key => $details) { - if (in_array($permission, $details['implied_permissions'] ?? [], TRUE)) { - $implied[$permission][] = $key; + try { + $permission = self::basicPermissions(TRUE, TRUE)[$permissionName] ?? NULL; + $impliedPermissions = array_merge([$permissionName], $permission['implied_by'] ?? []); + // Permission for a disabled component: always deny + if (!empty($permission['disabled'])) { + return [self::ALWAYS_DENY_PERMISSION]; } + // If it's a CiviCRM permission, then it's also implied by the master permission + elseif ($permission) { + $impliedPermissions[] = 'all CiviCRM permissions and ACLs'; + } + } + // This could happen early in the boot-cycle or during upgrade + catch (RuntimeException $e) { + $impliedPermissions = [$permissionName, 'all CiviCRM permissions and ACLs']; } - Civi::cache('metadata')->set('implied_permissions', $implied); - return $implied[$permission]; + return $impliedPermissions; } /** @@ -1752,32 +1823,20 @@ public static function checkDownloadInvoice() { /** * Get permissions for components. * - * @param bool $includeDisabled - * * @return array - * @throws \CRM_Core_Exception */ - protected static function getComponentPermissions(bool $includeDisabled): array { - if (!$includeDisabled) { - $components = CRM_Core_Component::getEnabledComponents(); - } - else { - $components = CRM_Core_Component::getComponents(); - } - + protected static function getComponentPermissions(): array { $permissions = []; - foreach ($components as $comp) { - $perm = $comp->getPermissions($includeDisabled, TRUE); - if ($perm) { - $info = $comp->getInfo(); - foreach ($perm as $p => $attr) { - - if (!is_array($attr)) { - $attr = [$attr]; + foreach (CRM_Core_Component::getComponents() as $component) { + $perms = $component->getPermissions(); + if ($perms) { + $info = $component->getInfo(); + foreach ($perms as $name => $perm) { + $perm['label'] = $info['translatedName'] . ': ' . $perm['label']; + if (!$component->isEnabled()) { + $perm['disabled'] = TRUE; } - - $attr[0] = $info['translatedName'] . ': ' . $attr[0]; - $permissions[$p] = $attr; + $permissions[$name] = $perm; } } } @@ -1787,15 +1846,11 @@ protected static function getComponentPermissions(bool $includeDisabled): array /** * Get permissions for core functionality and for that of core components. * - * @param bool $all - * * @return array - * @throws \CRM_Core_Exception */ - protected static function getCoreAndComponentPermissions(bool $all): array { + protected static function getCoreAndComponentPermissions(): array { $permissions = self::getCorePermissions(); - $permissions = array_merge($permissions, self::getComponentPermissions($all)); - $permissions['all CiviCRM permissions and ACLs']['implied_permissions'] = array_keys($permissions); + $permissions = array_merge($permissions, self::getComponentPermissions()); return $permissions; } diff --git a/www/modules/civicrm/CRM/Core/Permission/Backdrop.php b/www/modules/civicrm/CRM/Core/Permission/Backdrop.php index 6fc230a4a..ce8ab39c6 100644 --- a/www/modules/civicrm/CRM/Core/Permission/Backdrop.php +++ b/www/modules/civicrm/CRM/Core/Permission/Backdrop.php @@ -105,7 +105,7 @@ public function getAvailablePermissions() { // We want to list *only* Backdrop perms, so we'll *skip* Civi perms. $allCorePerms = \CRM_Core_Permission::basicPermissions(TRUE); - $permissions = []; + $permissions = parent::getAvailablePermissions(); $modules = system_get_info('module'); foreach ($modules as $moduleName => $module) { $prefix = isset($module['name']) ? ($module['name'] . ': ') : ''; diff --git a/www/modules/civicrm/CRM/Core/Permission/Base.php b/www/modules/civicrm/CRM/Core/Permission/Base.php index ad5966288..bc5e06e92 100644 --- a/www/modules/civicrm/CRM/Core/Permission/Base.php +++ b/www/modules/civicrm/CRM/Core/Permission/Base.php @@ -60,14 +60,12 @@ public function translatePermission($perm, $nativePrefix, $map) { [$civiPrefix, $name] = CRM_Utils_String::parsePrefix(':', $perm, NULL); switch ($civiPrefix) { case $nativePrefix: + case NULL: return $name; // pass through case 'cms': - return CRM_Utils_Array::value($name, $map, CRM_Core_Permission::ALWAYS_DENY_PERMISSION); - - case NULL: - return $name; + return $map[$name] ?? CRM_Core_Permission::ALWAYS_DENY_PERMISSION; default: return CRM_Core_Permission::ALWAYS_DENY_PERMISSION; @@ -310,7 +308,21 @@ public function checkGroupRole($array) { * @see \CRM_Core_Permission_Base::translatePermission() */ public function getAvailablePermissions() { - return []; + // These "synthetic" permissions are translated to the relevant CMS permissions in CRM/Core/Permission/*.php + // using the `translatePermission()` mechanism. + // EXCEPT in Standalone, where they are not needed + return [ + 'cms:view user account' => [ + 'title' => ts('CMS') . ': ' . ts('View user accounts'), + 'description' => ts('View user accounts. (Synthetic permission - adapts to local CMS)'), + 'is_synthetic' => TRUE, + ], + 'cms:administer users' => [ + 'title' => ts('CMS') . ': ' . ts('Administer user accounts'), + 'description' => ts('Administer user accounts. (Synthetic permission - adapts to local CMS)'), + 'is_synthetic' => TRUE, + ], + ]; } /** @@ -392,25 +404,30 @@ public function getModulePermissions($module): array { * Get the permissions defined in the hook_civicrm_permission implementation * in all enabled CiviCRM module extensions. * - * @param bool $descriptions - * * @return array * Array of permissions, in the same format as CRM_Core_Permission::getCorePermissions(). + * @throws RuntimeException */ - public function getAllModulePermissions($descriptions = FALSE): array { + public function getAllModulePermissions(): array { $permissions = []; CRM_Utils_Hook::permission($permissions); - if ($descriptions) { - foreach ($permissions as $permission => $label) { - $permissions[$permission] = (is_array($label)) ? $label : [$label]; - } - } - else { - // Passing in false here is to be deprecated. - foreach ($permissions as $permission => $label) { - $permissions[$permission] = (is_array($label)) ? array_shift($label) : $label; + // Normalize permission array format. + // Historically, a string was acceptable (interpreted as label), as was a non-associative array. + // Convert them all to associative arrays. + foreach ($permissions as $name => $defn) { + $defn = (array) $defn; + if (!isset($defn['label'])) { + CRM_Core_Error::deprecatedWarning("Permission '$name' should be declared with 'label' and 'description' keys. See https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_permission/"); } + $permission = [ + 'label' => $defn['label'] ?? $defn[0], + 'description' => $defn['description'] ?? $defn[1] ?? NULL, + 'disabled' => $defn['disabled'] ?? NULL, + 'implies' => $defn['implies'] ?? NULL, + 'implied_by' => $defn['implied_by'] ?? NULL, + ]; + $permissions[$name] = array_filter($permission, fn($item) => isset($item)); } return $permissions; } diff --git a/www/modules/civicrm/CRM/Core/Permission/Drupal.php b/www/modules/civicrm/CRM/Core/Permission/Drupal.php index 0b0abe0b0..279661d9e 100644 --- a/www/modules/civicrm/CRM/Core/Permission/Drupal.php +++ b/www/modules/civicrm/CRM/Core/Permission/Drupal.php @@ -104,7 +104,7 @@ public function getAvailablePermissions() { // We want to list *only* Drupal perms, so we'll *skip* Civi perms. $allCorePerms = \CRM_Core_Permission::basicPermissions(TRUE); - $permissions = []; + $permissions = parent::getAvailablePermissions(); $modules = system_get_info('module'); foreach ($modules as $moduleName => $module) { $prefix = isset($module['name']) ? ($module['name'] . ': ') : ''; diff --git a/www/modules/civicrm/CRM/Core/Permission/Drupal8.php b/www/modules/civicrm/CRM/Core/Permission/Drupal8.php index eb1d570c3..f07f8cb6b 100644 --- a/www/modules/civicrm/CRM/Core/Permission/Drupal8.php +++ b/www/modules/civicrm/CRM/Core/Permission/Drupal8.php @@ -33,6 +33,7 @@ class CRM_Core_Permission_Drupal8 extends CRM_Core_Permission_DrupalBase { public function check($str, $userId = NULL) { $str = $this->translatePermission($str, 'Drupal', [ 'view user account' => 'access user profiles', + 'administer users' => 'administer users', ]); if ($str == CRM_Core_Permission::ALWAYS_DENY_PERMISSION) { @@ -60,7 +61,7 @@ public function getAvailablePermissions() { $dperms = \Drupal::service('user.permissions')->getPermissions(); $modules = \Drupal::service('extension.list.module')->getAllInstalledInfo(); - $permissions = []; + $permissions = parent::getAvailablePermissions(); foreach ($dperms as $permName => $dperm) { if (isset($allCorePerms[$permName])) { continue; diff --git a/www/modules/civicrm/CRM/Core/Permission/List.php b/www/modules/civicrm/CRM/Core/Permission/List.php index 513828630..bb300ebfa 100644 --- a/www/modules/civicrm/CRM/Core/Permission/List.php +++ b/www/modules/civicrm/CRM/Core/Permission/List.php @@ -31,14 +31,14 @@ class CRM_Core_Permission_List { * @see \CRM_Utils_Hook::permissionList */ public static function findCiviPermissions(GenericHookEvent $e) { - $activeCorePerms = \CRM_Core_Permission::basicPermissions(FALSE); $allCorePerms = \CRM_Core_Permission::basicPermissions(TRUE, TRUE); foreach ($allCorePerms as $permName => $corePerm) { $e->permissions[$permName] = [ 'group' => 'civicrm', - 'title' => $corePerm['label'] ?? $corePerm[0] ?? $permName, - 'description' => $corePerm['description'] ?? $corePerm[1] ?? NULL, - 'is_active' => isset($activeCorePerms[$permName]), + 'title' => $corePerm['label'], + 'description' => $corePerm['description'] ?? NULL, + 'is_active' => empty($corePerm['disabled']), + 'implies' => $corePerm['implies'] ?? NULL, ]; } } @@ -54,28 +54,15 @@ public static function findCmsPermissions(GenericHookEvent $e) { $config = \CRM_Core_Config::singleton(); $ufPerms = $config->userPermissionClass->getAvailablePermissions(); + foreach ($ufPerms as $permName => $cmsPerm) { $e->permissions[$permName] = [ 'group' => 'cms', 'title' => $cmsPerm['title'] ?? $permName, 'description' => $cmsPerm['description'] ?? NULL, + 'is_synthetic' => $cmsPerm['is_synthetic'] ?? FALSE, ]; } - - // There are a handful of special permissions defined in CRM/Core/Permission/*.php - // using the `translatePermission()` mechanism. - $e->permissions['cms:view user account'] = [ - 'group' => 'cms', - 'title' => ts('CMS') . ': ' . ts('View user accounts'), - 'description' => ts('View user accounts. (Synthetic permission - adapts to local CMS)'), - 'is_synthetic' => TRUE, - ]; - $e->permissions['cms:administer users'] = [ - 'group' => 'cms', - 'title' => ts('CMS') . ': ' . ts('Administer user accounts'), - 'description' => ts('Administer user accounts. (Synthetic permission - adapts to local CMS)'), - 'is_synthetic' => TRUE, - ]; } /** @@ -84,6 +71,7 @@ public static function findCmsPermissions(GenericHookEvent $e) { */ public static function findConstPermissions(GenericHookEvent $e) { // There are a handful of special permissions defined in CRM/Core/Permission. + // Enforcement of them is handled in `CRM_Core_Permission_*::check()` $e->permissions[\CRM_Core_Permission::ALWAYS_DENY_PERMISSION] = [ 'group' => 'const', 'title' => ts('Generic: Deny all users'), @@ -93,6 +81,9 @@ public static function findConstPermissions(GenericHookEvent $e) { 'group' => 'const', 'title' => ts('Generic: Allow all users (including anonymous)'), 'is_synthetic' => TRUE, + // This line is here more as a bit of documentation (so it will show in `Civi\Api4\Permission::get()`). + // The functionality that actually handles this pseudo-permission is in `CRM_Core_Permission_*::check()` + 'implies' => ['*'], ]; } diff --git a/www/modules/civicrm/CRM/Core/Permission/Standalone.php b/www/modules/civicrm/CRM/Core/Permission/Standalone.php index deca76aba..9c638a17f 100644 --- a/www/modules/civicrm/CRM/Core/Permission/Standalone.php +++ b/www/modules/civicrm/CRM/Core/Permission/Standalone.php @@ -33,6 +33,17 @@ class CRM_Core_Permission_Standalone extends CRM_Core_Permission_Base { */ public $permissions = NULL; + /** + * @inheritdoc + * on Standalone we don't want to add any "synthetic" cms permissions + * because we have concrete equivalents, provided by e.g. standaloneusers extension + * so we override the base class to suppress the synthetic ones + * + */ + public function getAvailablePermissions() { + return []; + } + /** * Given a permission string, check for access requirements. * @@ -48,14 +59,6 @@ class CRM_Core_Permission_Standalone extends CRM_Core_Permission_Base { * true if yes, else false */ public function check($str, $userId = NULL) { - // These core-defined synthetic permissions (which cannot be applied by our Role UI): - // cms:administer users - // cms:view user account - // need mapping to our concrete permissions (which can be applied to Roles) with the same names: - $str = $this->translatePermission($str, 'Standalone', [ - 'view user account' => 'view user account', - 'administer users' => 'administer users', - ]); return \Civi\Standalone\Security::singleton()->checkPermission($str, $userId); } diff --git a/www/modules/civicrm/CRM/Core/Permission/WordPress.php b/www/modules/civicrm/CRM/Core/Permission/WordPress.php index dfcc6c34a..8fec3f57d 100644 --- a/www/modules/civicrm/CRM/Core/Permission/WordPress.php +++ b/www/modules/civicrm/CRM/Core/Permission/WordPress.php @@ -103,7 +103,7 @@ function($str) { $wpCaps = array_unique(array_merge(array_keys($wpRole['capabilities']), $wpCaps)); } - $permissions = []; + $permissions = parent::getAvailablePermissions(); foreach ($wpCaps as $wpCap) { if (!in_array($wpCap, $mungedCorePerms)) { $permissions["WordPress:$wpCap"] = [ diff --git a/www/modules/civicrm/CRM/Core/PseudoConstant.php b/www/modules/civicrm/CRM/Core/PseudoConstant.php index 73275f3eb..725da239a 100644 --- a/www/modules/civicrm/CRM/Core/PseudoConstant.php +++ b/www/modules/civicrm/CRM/Core/PseudoConstant.php @@ -152,7 +152,7 @@ public static function get($daoName, $fieldName, $params = [], $context = NULL) 'condition' => [], 'values' => [], ]; - $entity = CRM_Core_DAO_AllCoreTables::getBriefName($daoName); + $entity = CRM_Core_DAO_AllCoreTables::getEntityNameForClass($daoName); // Custom fields are not in the schema if (strpos($fieldName, 'custom_') === 0 && is_numeric($fieldName[7])) { diff --git a/www/modules/civicrm/CRM/Core/Reference/Basic.php b/www/modules/civicrm/CRM/Core/Reference/Basic.php index ab2c30984..1b15ea9e2 100644 --- a/www/modules/civicrm/CRM/Core/Reference/Basic.php +++ b/www/modules/civicrm/CRM/Core/Reference/Basic.php @@ -42,6 +42,9 @@ public function getReferenceKey() { } /** + * CRM_Core_Reference_Basic returns NULL. + * CRM_Core_Reference_Dynamic returns the name of the dynamic column e.g. "entity_table". + * * @return string|null */ public function getTypeColumn() { diff --git a/www/modules/civicrm/CRM/Core/Reference/Dynamic.php b/www/modules/civicrm/CRM/Core/Reference/Dynamic.php index dd192d58c..771081243 100644 --- a/www/modules/civicrm/CRM/Core/Reference/Dynamic.php +++ b/www/modules/civicrm/CRM/Core/Reference/Dynamic.php @@ -13,20 +13,37 @@ class CRM_Core_Reference_Dynamic extends CRM_Core_Reference_Basic { * @return bool */ public function matchesTargetTable($tableName) { - // FIXME: Shouldn't this check against keys returned by getTargetEntities? - return TRUE; + $targetEntities = $this->getTargetEntities(); + if (!$targetEntities) { + // Missing whitelist! That's not good, but we'll grandfather it in by accepting all entities. + return TRUE; + } + return in_array(CRM_Core_DAO_AllCoreTables::getEntityNameForTable($tableName), $targetEntities, TRUE); } /** + * Returns a list of all allowed values for $this->refTypeColumn + * * @return array - * [table_name => EntityName] + * [ref_column_value => EntityName] + * Keys are the value stored in $this->refTypeColumn, + * Values are the name of the corresponding entity. */ public function getTargetEntities(): array { $targetEntities = []; $bao = CRM_Core_DAO_AllCoreTables::getClassForTable($this->refTable); - $targetTables = $bao::buildOptions($this->refTypeColumn) ?: []; - foreach ($targetTables as $table => $label) { - $targetEntities[$table] = CRM_Core_DAO_AllCoreTables::getEntityNameForTable($table); + $targetTables = $bao::buildOptions($this->refTypeColumn, 'validate') ?: []; + foreach ($targetTables as $value => $name) { + // Old-style: Flat arrays of ['table_name' => 'Entity Label'] + // will be formatted like ['table_name' => 'table_name'] in 'validate' mode. + if (strtolower($value) === $name) { + $targetEntities[$value] = CRM_Core_DAO_AllCoreTables::getEntityNameForTable($value); + } + // New-style: ['id' => 'value', 'name' => 'EntityName', 'label' => 'Entity Label'][] + // will be formatted like ['value' => 'EntityName'] in 'validate' mode. + else { + $targetEntities[$value] = $name; + } } return $targetEntities; } @@ -40,23 +57,14 @@ public function getTargetEntities(): array { * a query-handle (like the result of CRM_Core_DAO::executeQuery) */ public function findReferences($targetDao) { - $refColumn = $this->getReferenceKey(); - $targetColumn = $this->getTargetKey(); - - $params = [ - 1 => [$targetDao->$targetColumn, 'String'], - // If anyone complains about $targetDao::getTableName(), then could use - // "{get_class($targetDao)}::getTableName();" - 2 => [$targetDao::getTableName(), 'String'], - ]; - $sql = <<getReferenceTable()} -WHERE {$refColumn} = %1 +WHERE {$this->getReferenceKey()} = %1 AND {$this->getTypeColumn()} = %2 EOS; + $params = $this->getQueryParams($targetDao); $daoName = CRM_Core_DAO_AllCoreTables::getClassForTable($this->getReferenceTable()); $result = CRM_Core_DAO::executeQuery($sql, $params, TRUE, $daoName); return $result; @@ -68,14 +76,6 @@ public function findReferences($targetDao) { * @return array */ public function getReferenceCount($targetDao) { - $targetColumn = $this->getTargetKey(); - $params = [ - 1 => [$targetDao->$targetColumn, 'String'], - // If anyone complains about $targetDao::getTableName(), then could use - // "{get_class($targetDao)}::getTableName();" - 2 => [$targetDao::getTableName(), 'String'], - ]; - $sql = <<getReferenceTable()} @@ -88,7 +88,28 @@ public function getReferenceCount($targetDao) { 'type' => get_class($this), 'table' => $this->getReferenceTable(), 'key' => $this->getReferenceKey(), - 'count' => CRM_Core_DAO::singleValueQuery($sql, $params), + 'count' => CRM_Core_DAO::singleValueQuery($sql, $this->getQueryParams($targetDao)), + ]; + } + + /** + * Gets query params needed by the find reference query + * @param CRM_Core_DAO $targetDao + * @return array[] + */ + private function getQueryParams($targetDao): array { + $targetColumn = $this->getTargetKey(); + + // Look up option value for this entity. It's usually the table name, but not always. + // If the lookup fails (some entities are missing the option list for the ref column), + // then fall back on the table name. + $targetEntity = CRM_Core_DAO_AllCoreTables::getEntityNameForClass(get_class($targetDao)); + $targetEntities = $this->getTargetEntities(); + $targetValue = array_search($targetEntity, $targetEntities) ?: $targetDao::getTableName(); + + return [ + 1 => [$targetDao->$targetColumn, 'String'], + 2 => [$targetValue, 'String'], ]; } diff --git a/www/modules/civicrm/CRM/Core/Region.php b/www/modules/civicrm/CRM/Core/Region.php index 1f12c8105..be78e450f 100644 --- a/www/modules/civicrm/CRM/Core/Region.php +++ b/www/modules/civicrm/CRM/Core/Region.php @@ -61,6 +61,10 @@ public function render($default, $allowCmsOverride = TRUE) { $this->snippets['default']['markup'] = $default; } + if (defined('CIVICRM_IFRAME')) { + $allowCmsOverride = FALSE; + } + Civi::dispatcher()->dispatch('civi.region.render', \Civi\Core\Event\GenericHookEvent::create(['region' => $this])); $this->sort(); @@ -139,7 +143,7 @@ public function render($default, $allowCmsOverride = TRUE) { break; case 'settings': - $settingsData = json_encode($this->getSettings(), JSON_UNESCAPED_SLASHES); + $settingsData = json_encode($this->getSettings()); $js = "(function(vars) { if (window.CRM) CRM.$.extend(true, CRM, vars); else window.CRM = vars; })($settingsData)"; diff --git a/www/modules/civicrm/CRM/Core/Resources.php b/www/modules/civicrm/CRM/Core/Resources.php index 3d3db50d7..97609ecdc 100644 --- a/www/modules/civicrm/CRM/Core/Resources.php +++ b/www/modules/civicrm/CRM/Core/Resources.php @@ -381,9 +381,11 @@ public function addCoreResources($region = 'html-header') { if (!self::isAjaxMode()) { $this->addBundle('coreResources'); $this->addCoreStyles($region); - // This ensures that if a popup link requires AngularJS, it will always be available. - // Additional Ang modules required by popups will be loaded on-the-fly by Civi\Angular\AngularLoader - Civi::service('angularjs.loader')->addModules(['crmResource']); + if (!CRM_Core_Config::isUpgradeMode()) { + // This ensures that if a popup link requires AngularJS, it will always be available. + // Additional Ang modules required by popups will be loaded on-the-fly by Civi\Angular\AngularLoader + Civi::service('angularjs.loader')->addModules(['crmResource']); + } } return $this; } diff --git a/www/modules/civicrm/CRM/Core/ScheduledJob.php b/www/modules/civicrm/CRM/Core/ScheduledJob.php index 02e518479..5a257ef68 100644 --- a/www/modules/civicrm/CRM/Core/ScheduledJob.php +++ b/www/modules/civicrm/CRM/Core/ScheduledJob.php @@ -153,6 +153,17 @@ public function saveLastRun() { $dao = new CRM_Core_DAO_Job(); $dao->id = $this->id; $dao->last_run = CRM_Utils_Date::currentDBDate(); + $dao->last_run_end = NULL; + $dao->save(); + } + + /** + * Update the last_run date of this job + */ + public function saveLastRunEnd() { + $dao = new CRM_Core_DAO_Job(); + $dao->id = $this->id; + $dao->last_run_end = CRM_Utils_Date::currentDBDate(); $dao->save(); } diff --git a/www/modules/civicrm/CRM/Core/SelectValues.php b/www/modules/civicrm/CRM/Core/SelectValues.php index 1384b0375..07bae3f65 100644 --- a/www/modules/civicrm/CRM/Core/SelectValues.php +++ b/www/modules/civicrm/CRM/Core/SelectValues.php @@ -369,8 +369,8 @@ public static function date($type = NULL, $format = NULL, $minOffset = NULL, $ma public static function ufVisibility() { return [ 'User and User Admin Only' => ts('User and User Admin Only'), - 'Public Pages' => ts('Expose Publicly'), - 'Public Pages and Listings' => ts('Expose Publicly and for Listings'), + 'Public Pages' => ts('Public Pages'), + 'Public Pages and Listings' => ts('Public Pages and Listings'), ]; } @@ -494,7 +494,7 @@ public static function addressProvider() { } public static function smsProvider(): array { - $providers = CRM_SMS_BAO_Provider::getProviders(NULL, NULL, TRUE, 'is_default desc, title'); + $providers = CRM_SMS_BAO_SmsProvider::getProviders(NULL, NULL, TRUE, 'is_default desc, title'); $result = []; foreach ($providers as $provider) { $result[] = [ @@ -1118,7 +1118,7 @@ public static function getDashboardEntriesCount() { } public static function getQuicksearchOptions(): array { - $includeEmail = civicrm_api3('setting', 'getvalue', ['name' => 'includeEmailInName', 'group' => 'Search Preferences']); + $includeEmail = Civi::settings()->get('includeEmailInName'); $options = [ [ 'key' => 'sort_name', @@ -1174,27 +1174,18 @@ public static function getQuicksearchOptions(): array { 'label' => ts('Job Title'), ], ]; - $custom = civicrm_api4('CustomField', 'get', [ - 'checkPermissions' => FALSE, - 'select' => ['id', 'name', 'label', 'custom_group_id.name', 'custom_group_id.title', 'option_group_id'], - 'where' => [ - ['custom_group_id.extends', 'IN', array_merge(['Contact'], CRM_Contact_BAO_ContactType::basicTypes())], - ['data_type', 'NOT IN', ['ContactReference', 'Date', 'File']], - ['custom_group_id.is_active', '=', TRUE], - ['is_active', '=', TRUE], - ['is_searchable', '=', TRUE], - ], - 'orderBy' => [ - 'custom_group_id.weight' => 'ASC', - 'weight' => 'ASC', - ], - ]); - foreach ($custom as $field) { - $options[] = [ - 'key' => $field['custom_group_id.name'] . '.' . $field['name'] . ($field['option_group_id'] ? ':label' : ''), - 'label' => $field['custom_group_id.title'] . ': ' . $field['label'], - 'adv_search_legacy' => 'custom_' . $field['id'], - ]; + $customGroups = CRM_Core_BAO_CustomGroup::getAll(['extends' => 'Contact', 'is_active' => TRUE], CRM_Core_Permission::VIEW); + foreach ($customGroups as $group) { + foreach ($group['fields'] as $field) { + if (in_array($field['data_type'], ['Date', 'File', 'ContactReference', 'EntityReference'])) { + continue; + } + $options[] = [ + 'key' => $group['name'] . '.' . $field['name'] . ($field['option_group_id'] ? ':label' : ''), + 'label' => $group['title'] . ': ' . $field['label'], + 'adv_search_legacy' => 'custom_' . $field['id'], + ]; + } } return $options; } diff --git a/www/modules/civicrm/CRM/Core/Selector/Controller.php b/www/modules/civicrm/CRM/Core/Selector/Controller.php index b2d899a30..a4518b3fc 100644 --- a/www/modules/civicrm/CRM/Core/Selector/Controller.php +++ b/www/modules/civicrm/CRM/Core/Selector/Controller.php @@ -319,8 +319,8 @@ public function run() { } else { // assign to template and display them. - self::$_template->assign_by_ref('rows', $rows); - self::$_template->assign_by_ref('columnHeaders', $columnHeaders); + self::$_template->assign('rows', $rows); + self::$_template->assign('columnHeaders', $columnHeaders); } } else { @@ -359,11 +359,11 @@ public function run() { $this->_store->set("{$this->_prefix}summary", $summary); } else { - self::$_template->assign_by_ref("{$this->_prefix}pager", $this->_pager); - self::$_template->assign_by_ref("{$this->_prefix}sort", $this->_sort); + self::$_template->assign("{$this->_prefix}pager", $this->_pager); + self::$_template->assign("{$this->_prefix}sort", $this->_sort); - self::$_template->assign_by_ref("{$this->_prefix}columnHeaders", $finalColumnHeaders); - self::$_template->assign_by_ref("{$this->_prefix}rows", $rows); + self::$_template->assign("{$this->_prefix}columnHeaders", $finalColumnHeaders); + self::$_template->assign("{$this->_prefix}rows", $rows); self::$_template->assign("{$this->_prefix}rowsEmpty", !$rows); self::$_template->assign("{$this->_prefix}qill", $qill); self::$_template->assign("{$this->_prefix}summary", $summary); @@ -450,7 +450,7 @@ public function getSort() { * @return void */ public function moveFromSessionToTemplate() { - self::$_template->assign_by_ref("{$this->_prefix}pager", $this->_pager); + self::$_template->assign("{$this->_prefix}pager", $this->_pager); $rows = $this->_store->get("{$this->_prefix}rows"); @@ -464,7 +464,7 @@ public function moveFromSessionToTemplate() { ); } - self::$_template->assign_by_ref("{$this->_prefix}sort", $this->_sort); + self::$_template->assign("{$this->_prefix}sort", $this->_sort); $columnHeaders = (array) $this->_store->get("{$this->_prefix}columnHeaders"); foreach ($columnHeaders as $index => $columnHeader) { // Fill out the keys to avoid e-notices. diff --git a/www/modules/civicrm/CRM/Core/Session.php b/www/modules/civicrm/CRM/Core/Session.php index 5f5f46589..a84566d84 100644 --- a/www/modules/civicrm/CRM/Core/Session.php +++ b/www/modules/civicrm/CRM/Core/Session.php @@ -550,12 +550,9 @@ public static function storeSessionObjects($reset = TRUE) { * @return int|null * contact ID of logged in user */ - public static function getLoggedInContactID() { - $session = CRM_Core_Session::singleton(); - if (!is_numeric($session->get('userID'))) { - return NULL; - } - return (int) $session->get('userID'); + public static function getLoggedInContactID(): ?int { + $userId = CRM_Core_Session::singleton()->get('userID'); + return is_numeric($userId) ? (int) $userId : NULL; } /** @@ -565,12 +562,12 @@ public static function getLoggedInContactID() { * * @throws CRM_Core_Exception */ - public function getLoggedInContactDisplayName() { + public function getLoggedInContactDisplayName(): string { $userContactID = CRM_Core_Session::getLoggedInContactID(); if (!$userContactID) { return ''; } - return civicrm_api3('Contact', 'getvalue', ['id' => $userContactID, 'return' => 'display_name']); + return CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $userContactID, 'display_name') ?? ''; } /** diff --git a/www/modules/civicrm/CRM/Core/ShowHideBlocks.php b/www/modules/civicrm/CRM/Core/ShowHideBlocks.php index 8c490ac43..4c5f4f90a 100644 --- a/www/modules/civicrm/CRM/Core/ShowHideBlocks.php +++ b/www/modules/civicrm/CRM/Core/ShowHideBlocks.php @@ -82,8 +82,8 @@ public function addToTemplate() { $template = CRM_Core_Smarty::singleton(); $template->ensureVariablesAreAssigned(['elemType']); - $template->assign_by_ref('hideBlocks', $hide); - $template->assign_by_ref('showBlocks', $show); + $template->assign('hideBlocks', $hide); + $template->assign('showBlocks', $show); } /** diff --git a/www/modules/civicrm/CRM/Core/Smarty.php b/www/modules/civicrm/CRM/Core/Smarty.php index 3c0ad83dc..e2edcef04 100644 --- a/www/modules/civicrm/CRM/Core/Smarty.php +++ b/www/modules/civicrm/CRM/Core/Smarty.php @@ -72,26 +72,43 @@ class CRM_Core_Smarty extends CRM_Core_SmartyCompatibility { */ private static $UNDEFINED_VALUE; + /** + * @throws \CRM_Core_Exception + * @throws \SmartyException + */ private function initialize() { $config = CRM_Core_Config::singleton(); if (isset($config->customTemplateDir) && $config->customTemplateDir) { - $this->template_dir = array_merge([$config->customTemplateDir], + $template_dir = array_merge([$config->customTemplateDir], $config->templateDir ); } else { - $this->template_dir = $config->templateDir; + $template_dir = $config->templateDir; + } + $compile_dir = CRM_Utils_File::addTrailingSlash(CRM_Utils_File::addTrailingSlash($config->templateCompileDir) . $this->getLocale()); + + if (!defined('SMARTY_DIR')) { + // The absence of the global indicates Smarty5 - which is not a fan of bypassing the functions. + // In theory this would work on all & it does run fun with Smarty2 but our tests + // do something weird with loading Smarty so we have to head tests running Smarty2 off + // at the pass. + $this->setTemplateDir($template_dir); + $this->setCompileDir($compile_dir); } - $this->compile_dir = CRM_Utils_File::addTrailingSlash(CRM_Utils_File::addTrailingSlash($config->templateCompileDir) . $this->getLocale()); - CRM_Utils_File::createDir($this->compile_dir); - CRM_Utils_File::restrictAccess($this->compile_dir); + else { + $this->template_dir = $template_dir; + $this->compile_dir = $compile_dir; + } + CRM_Utils_File::createDir($compile_dir); + CRM_Utils_File::restrictAccess($compile_dir); // check and ensure it is writable // else we sometime suppress errors quietly and this results // in blank emails etc - if (!is_writable($this->compile_dir)) { - echo "CiviCRM does not have permission to write temp files in {$this->compile_dir}, Exiting"; + if (!is_writable($compile_dir)) { + echo "CiviCRM does not have permission to write temp files in {$compile_dir}, Exiting"; exit(); } @@ -108,18 +125,16 @@ private function initialize() { if (!file_exists($customPluginsDir)) { $customPluginsDir = NULL; } + if ($customPluginsDir) { + $this->addPluginsDir($customPluginsDir); + } } $pkgsDir = Civi::paths()->getVariable('civicrm.packages', 'path'); - $smartyDir = $pkgsDir . DIRECTORY_SEPARATOR . 'Smarty' . DIRECTORY_SEPARATOR; - $pluginsDir = __DIR__ . DIRECTORY_SEPARATOR . 'Smarty' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR; - - if ($customPluginsDir) { - $this->plugins_dir = [$customPluginsDir, $smartyDir . 'plugins', $pluginsDir]; - } - else { - $this->plugins_dir = [$smartyDir . 'plugins', $pluginsDir]; - } + // smarty3/4 have the define, fall back to smarty2. smarty5 deprecates plugins_dir - TBD. + $smartyPluginsDir = defined('SMARTY_PLUGINS_DIR') ? SMARTY_PLUGINS_DIR : ($pkgsDir . DIRECTORY_SEPARATOR . 'Smarty' . DIRECTORY_SEPARATOR . 'plugins'); + $corePluginsDir = __DIR__ . DIRECTORY_SEPARATOR . 'Smarty' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR; + $this->addPluginsDir($corePluginsDir); $this->compile_check = $this->isCheckSmartyIsCompiled(); @@ -147,7 +162,9 @@ private function initialize() { } if (CRM_Utils_Constant::value('CIVICRM_SMARTY_DEFAULT_ESCAPE') - && !CRM_Utils_Constant::value('CIVICRM_SMARTY3_AUTOLOAD_PATH')) { + && !CRM_Utils_Constant::value('CIVICRM_SMARTY3_AUTOLOAD_PATH') + && !CRM_Utils_Constant::value('CIVICRM_SMARTY_AUTOLOAD_PATH') + ) { // Currently DEFAULT escape does not work with Smarty3 // dunno why - thought it would be the default with Smarty3 - but // getting onto Smarty 3 is higher priority. @@ -158,20 +175,75 @@ private function initialize() { // contribution dashboard from RecentlyViewed.tpl require_once 'Smarty/plugins/modifier.escape.php'; if (!isset($this->_plugins['modifier']['escape'])) { - $this->register_modifier('escape', ['CRM_Core_Smarty', 'escape']); + $this->registerPlugin('modifier', 'escape', ['CRM_Core_Smarty', 'escape']); } $this->default_modifiers[] = 'escape:"htmlall"'; } - $this->load_filter('pre', 'resetExtScope'); - $this->load_filter('pre', 'htxtFilter'); + $this->loadFilter('pre', 'resetExtScope'); + $this->loadFilter('pre', 'htxtFilter'); + + // Smarty5 can't use php functions unless they are registered.... Smarty4 gets noisy about it. + $functionsForSmarty = [ + // In theory json_encode, count & implode no longer need to + // be added as they are now more natively supported in smarty4, smarty5 + 'json_encode', + 'count', + 'implode', + // We use str_starts_with to check if a field is (e.g 'phone_' in profile presentation. + 'str_starts_with', + // Trim is used on the extensions page. + 'trim', + 'is_numeric', + 'array_key_exists', + 'strstr', + 'strpos', + ]; + foreach ($functionsForSmarty as $function) { + $this->registerPlugin('modifier', $function, $function); + } + $this->registerPlugin('modifier', 'call_user_func', [self::class, 'callUserFuncArray']); + // This does not appear to be used & feels like the sort of approach that would be phased out. $this->assign('crmPermissions', new CRM_Core_Smarty_Permissions()); - if ($config->debug || str_contains(CIVICRM_UF_BASEURL, 'localhost')) { + if ($config->debug || str_contains(CIVICRM_UF_BASEURL, 'localhost') || CRM_Utils_Constant::value('CIVICRM_UF') === 'UnitTests') { $this->error_reporting = E_ALL; } } + /** + * Call a permitted function from the Smarty layer. + * + * In general calling functions from the Smarty layer is being made stricter in + * Smarty - they need to be registered. + * + * We can't quite kill off call_user_func from the smarty layer yet but we + * can deprecate using it to call anything other than the 3 known patterns. + * In Smarty5 this will hard-fail, which is OK as Smarty5 is being phased in + * and can err on the side of strictness, at least for now. + * + * @param callable $callable + * @param mixed $args + * + * @return mixed + * @throws \CRM_Core_Exception + */ + public static function callUserFuncArray(callable $callable, ...$args) { + $permitted = [ + ['CRM_Campaign_BAO_Campaign', 'isComponentEnabled'], + ['CRM_Case_BAO_Case', 'checkPermission'], + ['CRM_Core_Permission', 'check'], + ['CRM_Core_Permission', 'access'], + ]; + if (!in_array($callable, $permitted)) { + if (CRM_Core_Smarty::singleton()->getVersion() === 5) { + throw new CRM_Core_Exception('unsupported function'); + } + CRM_Core_Error::deprecatedWarning('unsupported function. call_user_func array is not generally supported in Smarty5 but we have transitional support for 2 functions that are in common use'); + } + return call_user_func_array($callable, $args ?: []); + } + /** * Static instance provider. * @@ -280,48 +352,29 @@ public function appendValue($name, $value) { } } + /** + * Clear template variables, except session or config. + * + * Also the debugging variable because during test runs initialize() is only + * called once at the start but the var gets indirectly accessed by a couple + * tests that test forms. + * + * @return void + */ public function clearTemplateVars(): void { foreach (array_keys($this->getTemplateVars()) as $key) { - if ($key === 'config' || $key === 'session') { + if ($key === 'config' || $key === 'session' || $key === 'debugging') { continue; } - if (method_exists($this, 'clearAssign')) { - $this->clearAssign($key); - } - else { - $this->clear_assign($key); - } + $this->clearAssign($key); } } public static function registerStringResource() { - require_once 'CRM/Core/Smarty/resources/String.php'; - civicrm_smarty_register_string_resource(); - } - - /** - * Add template directory(s). - * - * @param string|array $template_dir directory(s) of template sources - * @param string $key (Smarty3+) of the array element to assign the template dir to - * @param bool $isConfig (Smarty3+) true for config_dir - * - * @return Smarty current Smarty instance for chaining - */ - public function addTemplateDir($template_dir, $key = NULL, $isConfig = FALSE) { - if (method_exists('parent', 'addTemplateDir')) { - // More recent versions of Smarty have this method. - return parent::addTemplateDir($template_dir, $key, $isConfig); + if (method_exists('Smarty', 'register_resource')) { + require_once 'CRM/Core/Smarty/resources/String.php'; + civicrm_smarty_register_string_resource(); } - if (is_array($this->template_dir)) { - if (!in_array($template_dir, $this->template_dir)) { - array_unshift($this->template_dir, $template_dir); - } - } - else { - $this->template_dir = [$template_dir, $this->template_dir]; - } - return $this; } /** @@ -376,7 +429,7 @@ public function assignAll($vars) { $this->assign($key, $value); } else { - $this->clear_assign($key); + $this->clearAssign($key); } } return $this; @@ -499,4 +552,28 @@ public static function escape($string, $esc_type = 'html', $char_set = 'UTF-8') return $value; } + public function getVersion (): int { + static $version; + if ($version === NULL) { + if (class_exists('Smarty\Smarty')) { + $version = 5; + } + else { + $class = new ReflectionClass('Smarty'); + $path = $class->getFileName(); + if (str_contains($path, 'smarty3')) { + $version = 3; + } + elseif (str_contains($path, 'smarty4')) { + $version = 4; + } + else { + $version = 2; + } + } + } + return $version; + + } + } diff --git a/www/modules/civicrm/CRM/Core/Smarty/UserContentPolicy.php b/www/modules/civicrm/CRM/Core/Smarty/UserContentPolicy.php new file mode 100644 index 000000000..f3a7bb702 --- /dev/null +++ b/www/modules/civicrm/CRM/Core/Smarty/UserContentPolicy.php @@ -0,0 +1,207 @@ + $instance]); + Civi::dispatcher()->dispatch('hook_civicrm_userContentPolicy', $event); + + return $instance; + } + + public function enable(): void { + $smarty = CRM_Core_Smarty::singleton(); + switch ($smarty->getVersion()) { + case 2: + $this->old_settings = $smarty->security_settings; + $smarty->security_settings = $this->createSmartyPolicy2($smarty); + $smarty->security = TRUE; + return; + + case 3: + case 4: + $smarty->enableSecurity($this->createSmartyPolicy34()); + return; + + case 5: + $smarty->enableSecurity($this->createSmartyPolicy5()); + return; + } + } + + public function disable(): void { + $smarty = CRM_Core_Smarty::singleton(); + switch ($smarty->getVersion()) { + case 2: + $smarty->security_settings = $this->old_settings; + $smarty->security = FALSE; + return; + + case 3: + case 4: + $smarty->disableSecurity(); + return; + + case 5: + $smarty->disableSecurity(); + return; + } + } + + protected function createSmartyPolicy2($smarty): array { + $result = $smarty->security_settings; + $result['IF_FUNCS'] = $this->php_functions; + $result['MODIFIER_FUNCS'] = $this->php_modifiers; + $result['ALLOW_CONSTANTS'] = $this->allow_constants; + $result['ALLOW_SUPER_GLOBALS'] = $this->allow_super_globals; + return $result; + } + + protected function createSmartyPolicy34(): string { + $obj = new class(NULL) extends Smarty_Security { + + public function __construct($smarty) { + parent::__construct($smarty); + + /** @var \CRM_Core_Smarty_UserContentPolicy $policy */ + $policy = Civi::service('civi.smarty.userContent'); + + $this->php_functions = $policy->php_functions; + $this->php_modifiers = $policy->php_modifiers; + $this->disabled_tags = $policy->disabled_tags; + + $this->static_classes = NULL; + $this->allow_constants = $policy->allow_constants; + $this->allow_super_globals = $policy->allow_super_globals; + } + + }; + return get_class($obj); + } + + protected function createSmartyPolicy5(): string { + + $obj = new class(NULL) extends \Smarty\Security { + + public function __construct($smarty) { + if ($smarty !== NULL) { + parent::__construct($smarty); + } + + /** @var \CRM_Core_Smarty_UserContentPolicy $policy */ + $policy = Civi::service('civi.smarty.userContent'); + + // This feels counterintuitive. Eileen thinks it may be a miscommunication. + // Functionally, consider that (a) security is enabled/disabled/enabled/disabled + // but (b) the registered plugins don't actually change. + + // foreach ($policy->php_functions as $phpFunction) { + // $smarty->registerPlugin('modifier', $phpFunction, $phpFunction); + // } + // foreach ($policy->php_modifiers as $modifier) { + // $smarty->registerPlugin('modifier', $modifier, $modifier); + // } + + $this->static_classes = NULL; + $this->allow_constants = $policy->allow_constants; + $this->allow_super_globals = $policy->allow_super_globals; + } + + }; + return get_class($obj); + } + + /** + * Smarty 3+4 have option to disable tags in secure mode, but Smarty 2 doesn't. + * So for any potentially-sensitive tags, we support an alternate mechanism to check access. + * + * @param string $tag + * @return void + * @throws \Exception + */ + public static function assertTagAllowed(string $tag): void { + if (!\Civi\Core\Container::isContainerBooted()) { + // We don't run user content before the system has booted. + // So the details here are kind of academic. + // Main thing: don't force a premature bootstrap. + $disabledTags = ['crmAPI']; + } + else { + $smarty = CRM_Core_Smarty::singleton(); + $hasSecurity = ($smarty->getVersion() > 2) ? (bool) $smarty->security_policy : $smarty->security; + if (!$hasSecurity) { + return; + } + + $policy = Civi::service('civi.smarty.userContent'); + $disabledTags = $policy->disabled_tags; + } + + if (in_array($tag, $disabledTags)) { + throw new \Exception("Tag '{$tag}' is not allowed in secure mode."); + } + } + +} diff --git a/www/modules/civicrm/CRM/Core/Smarty/plugins/block.crmScope.php b/www/modules/civicrm/CRM/Core/Smarty/plugins/block.crmScope.php index 3e0e02800..76662a47a 100644 --- a/www/modules/civicrm/CRM/Core/Smarty/plugins/block.crmScope.php +++ b/www/modules/civicrm/CRM/Core/Smarty/plugins/block.crmScope.php @@ -19,7 +19,7 @@ * Must define 'name'. * @param string $content * Default content. - * @param CRM_Core_Smarty $smarty + * @param \Smarty_Internal_Template $smarty * The Smarty object. * * @param $repeat @@ -29,13 +29,29 @@ function smarty_block_crmScope($params, $content, &$smarty, &$repeat) { /** @var CRM_Core_Smarty $smarty */ + if (!array_key_exists(__FUNCTION__, \Civi::$statics)) { + \Civi::$statics[__FUNCTION__] = []; + } + $backupFrames = &\Civi::$statics[__FUNCTION__]; if ($repeat) { - // open crmScope - $smarty->pushScope($params); + $templateVars = $smarty->getTemplateVars(); + $backupFrame = []; + foreach ($params as $name => $value) { + $backupFrame[$name] = array_key_exists($name, $templateVars) ? $templateVars[$name] : NULL; + $smarty->assign($name, $value); + } + $backupFrames[] = $backupFrame; } else { - // close crmScope - $smarty->popScope(); + $backedUpVariables = array_pop($backupFrames); + foreach ($backedUpVariables as $key => $value) { + if (array_key_exists($key, $params)) { + $smarty->assign($key, $value); + } + else { + $smarty->clearAssign($key); + } + } } return $content; diff --git a/www/modules/civicrm/CRM/Core/Smarty/plugins/block.crmUpgradeSnapshot.php b/www/modules/civicrm/CRM/Core/Smarty/plugins/block.crmUpgradeSnapshot.php index 44571c14e..cd21377dc 100644 --- a/www/modules/civicrm/CRM/Core/Smarty/plugins/block.crmUpgradeSnapshot.php +++ b/www/modules/civicrm/CRM/Core/Smarty/plugins/block.crmUpgradeSnapshot.php @@ -45,7 +45,7 @@ function smarty_block_crmUpgradeSnapshot($params, $text, &$smarty, &$repeat) { if (empty($params['name'])) { throw new \CRM_Core_Exception('Failed to process {crmUpgradeSnapshot}: Missing name'); } - if (empty($smarty->get_template_vars('upgradeRev'))) { + if (empty($smarty->getTemplateVars('upgradeRev'))) { throw new \CRM_Core_Exception('Failed to process {crmUpgradeSnapshot}: Upgrade context required. $upgradeRev missing.'); } if (!preg_match(';^\s*select\s;i', $text)) { @@ -53,7 +53,7 @@ function smarty_block_crmUpgradeSnapshot($params, $text, &$smarty, &$repeat) { } $owner = $params['owner'] ?? 'civicrm'; - $revParts = explode('.', $smarty->get_template_vars('upgradeRev')); + $revParts = explode('.', $smarty->getTemplateVars('upgradeRev')); $queries = CRM_Upgrade_Snapshot::createSingleTask($owner, $revParts[0] . '.' . $revParts[1], $params['name'], $text); return $queries ? (implode(";\n", $queries) . ";\n") : ""; } diff --git a/www/modules/civicrm/CRM/Core/Smarty/plugins/block.localize.php b/www/modules/civicrm/CRM/Core/Smarty/plugins/block.localize.php index 6b7f357d8..7d748271b 100644 --- a/www/modules/civicrm/CRM/Core/Smarty/plugins/block.localize.php +++ b/www/modules/civicrm/CRM/Core/Smarty/plugins/block.localize.php @@ -38,13 +38,13 @@ function smarty_block_localize($params, $text, $smarty, &$repeat) { // For opening tag text is always null return ''; } - $multiLingual = method_exists($smarty, 'get_template_vars') ? $smarty->get_template_vars('multilingual') : $smarty->getTemplateVars('multilingual'); + $multiLingual = $smarty->getTemplateVars('multilingual'); if (!$multiLingual) { return $text; } $lines = []; - $locales = (array) (method_exists($smarty, 'get_template_vars') ? $smarty->get_template_vars('locales') : $smarty->getTemplateVars('locales')); + $locales = (array) $smarty->getTemplateVars('locales'); foreach ($locales as $locale) { $line = $text; if (isset($params['field'])) { diff --git a/www/modules/civicrm/CRM/Core/Smarty/plugins/block.ts.php b/www/modules/civicrm/CRM/Core/Smarty/plugins/block.ts.php index 3c0016c6d..14b7ddd72 100644 --- a/www/modules/civicrm/CRM/Core/Smarty/plugins/block.ts.php +++ b/www/modules/civicrm/CRM/Core/Smarty/plugins/block.ts.php @@ -37,14 +37,7 @@ */ function smarty_block_ts($params, $text, &$smarty, &$repeat) { if (!$repeat) { - $extensionKey = ''; - if (method_exists($smarty, 'getTemplateVars')) { - $extensionKey = $smarty->getTemplateVars('extensionKey'); - } - else { - // Transitional support for Smarty2 still being used in GenCode. - $extensionKey = $smarty->get_template_vars('extensionKey'); - } + $extensionKey = $smarty->getTemplateVars('extensionKey'); if (!isset($params['domain']) && $extensionKey) { $params['domain'] = is_array($extensionKey) ? $extensionKey : [$extensionKey, NULL]; } diff --git a/www/modules/civicrm/CRM/Core/Smarty/plugins/function.crmAPI.php b/www/modules/civicrm/CRM/Core/Smarty/plugins/function.crmAPI.php index 26fe83405..95c925e3e 100644 --- a/www/modules/civicrm/CRM/Core/Smarty/plugins/function.crmAPI.php +++ b/www/modules/civicrm/CRM/Core/Smarty/plugins/function.crmAPI.php @@ -22,6 +22,8 @@ * @return string|void */ function smarty_function_crmAPI($params, &$smarty) { + CRM_Core_Smarty_UserContentPolicy::assertTagAllowed('crmAPI'); + if (!array_key_exists('entity', $params)) { $smarty->trigger_error("assign: missing 'entity' parameter"); return "crmAPI: missing 'entity' parameter"; diff --git a/www/modules/civicrm/CRM/Core/Smarty/plugins/function.crmSqlData.php b/www/modules/civicrm/CRM/Core/Smarty/plugins/function.crmSqlData.php index 3336fdd03..1842eede4 100644 --- a/www/modules/civicrm/CRM/Core/Smarty/plugins/function.crmSqlData.php +++ b/www/modules/civicrm/CRM/Core/Smarty/plugins/function.crmSqlData.php @@ -27,11 +27,9 @@ * @internal */ function smarty_function_crmSqlData($params, &$smarty) { - if ($smarty->security) { - throw new \CRM_Core_Exception("crmSqlData not allowed in secure mode"); - // In theory, there's nothing actually wrong with running in secure more. We just don't need it. - // If that changes, then be sure to double-check that the file-name sanitization is good. - } + CRM_Core_Smarty_UserContentPolicy::assertTagAllowed('crmSqlData'); + // In theory, there's nothing actually wrong with running in secure more. We just don't need it. + // If that changes, then be sure to double-check that the file-name sanitization is good. $civicrmDir = dirname(__DIR__, 4); $files = glob($civicrmDir . DIRECTORY_SEPARATOR . $params['file']); diff --git a/www/modules/civicrm/CRM/Core/Smarty/plugins/function.help.php b/www/modules/civicrm/CRM/Core/Smarty/plugins/function.help.php index 4bf211be6..360e6c82e 100644 --- a/www/modules/civicrm/CRM/Core/Smarty/plugins/function.help.php +++ b/www/modules/civicrm/CRM/Core/Smarty/plugins/function.help.php @@ -16,17 +16,24 @@ */ /** - * Adds inline help + * Adds inline help. + * + * This function adds a call to the js function which loads the help text in a pop-up. + * + * It does a lot of work to get the title which it passes into the crmHelp function + * but the main reason it gets that title is because it adds that to the css as + * title & aria-label. Since it's loaded it somewhat makes sense to pass it into + * CRM.help but .. it's confusing. * * @param array $params * The function params. - * @param CRM_Core_Smarty $smarty - * Reference to the smarty object. + * @param Smarty $smarty + * Smarty object. * * @return string * the help html to be inserted */ -function smarty_function_help($params, &$smarty) { +function smarty_function_help($params, $smarty) { if (!isset($params['id']) || !isset($smarty->getTemplateVars()['config'])) { return NULL; } @@ -41,7 +48,11 @@ function smarty_function_help($params, &$smarty) { $params['file'] = str_replace(['.tpl', '.hlp'], '', $params['file']); $fieldID = str_replace('-', '_', preg_replace('/^id-/', '', $params['id'])); - if (empty($params['title'])) { + if (!empty($params['title'])) { + // Passing in title is preferable..... ideally we would always pass in title & remove from the .hlp files.... + $helpTextTitle = trim(strip_tags($params['title'])) ?: $vars['form'][$fieldID]['textLabel'] ?? ''; + } + else { $vars = $smarty->getTemplateVars(); // The way this works is a bit bonkers. All the .hlp files are expecting an @@ -62,28 +73,36 @@ function smarty_function_help($params, &$smarty) { // overwrite it, so only do this if not already set. $temporary_vars += ['params' => []]; } - // Note fetchWith adds the temporary ones to the existing scope but then - // will reset, unsetting them if not already present before, which is what - // we want here. - $name = trim($smarty->fetchWith($params['file'] . '.hlp', $temporary_vars)) ?: $vars['form'][$fieldID]['textLabel'] ?? ''; - $additionalTPLFile = $params['file'] . '.extra.hlp'; - if ($smarty->template_exists($additionalTPLFile)) { - $extraoutput = trim($smarty->fetch($additionalTPLFile)); - if ($extraoutput) { - // Allow override param to replace default text e.g. {hlp id='foo' override=1} - $name = ($smarty->getTemplateVars('override_help_text') || empty($name)) ? $extraoutput : $name . ' ' . $extraoutput; + + $helpFile = $params['file'] . '.hlp'; + $additionalFile = $params['file'] . '.extra.hlp'; + $coreSmarty = CRM_Core_Smarty::singleton(); + $directories = $coreSmarty->getTemplateDir(); + $helpText = ''; + foreach ($directories as $directory) { + if (CRM_Utils_File::isIncludable($directory . $helpFile)) { + $helpText = file_get_contents($directory . $helpFile); + break; } } - - // Ensure we didn't change any existing vars CRM-11900 - foreach ($vars as $key => $value) { - if ($smarty->getTemplateVars($key) !== $value) { - $smarty->assign($key, $value); + $additionalTexts = []; + foreach ($directories as $directory) { + if (CRM_Utils_File::isIncludable($directory . $additionalFile)) { + $additionalTexts[] = file_get_contents($directory . $additionalFile); + break; } } - } - else { - $name = trim(strip_tags($params['title'])) ?: $vars['form'][$fieldID]['textLabel'] ?? ''; + try { + $coreSmarty->pushScope($temporary_vars); + $helpTextTitle = trim(CRM_Utils_String::parseOneOffStringThroughSmarty($helpText) ?: $vars['form'][$fieldID]['textLabel'] ?? ''); + foreach ($additionalTexts as $additionalText) { + $additionalTextTitle = trim(CRM_Utils_String::parseOneOffStringThroughSmarty($additionalText)); + $helpTextTitle = ($smarty->getTemplateVars('override_help_text') || empty($helpTextTitle)) ? $additionalTextTitle : $helpTextTitle . ' ' . $additionalTextTitle; + } + } + finally { + $coreSmarty->popScope(); + } } $class = "helpicon"; @@ -92,14 +111,14 @@ function smarty_function_help($params, &$smarty) { } // Escape for html - $title = htmlspecialchars(ts('%1 Help', [1 => $name])); + $title = htmlspecialchars(ts('%1 Help', [1 => $helpTextTitle])); // Escape for html and js - $name = htmlspecialchars(json_encode($name), ENT_QUOTES); + $helpTextTitle = htmlspecialchars(json_encode($helpTextTitle), ENT_QUOTES); // Format params to survive being passed through json & the url unset($params['text'], $params['title']); foreach ($params as &$param) { $param = is_bool($param) || is_numeric($param) ? (int) $param : (string) $param; } - return ' '; + return ' '; } diff --git a/www/modules/civicrm/CRM/Core/Smarty/plugins/modifier.crmEscapeSingleQuotes.php b/www/modules/civicrm/CRM/Core/Smarty/plugins/modifier.crmEscapeSingleQuotes.php new file mode 100644 index 000000000..90a90e3c9 --- /dev/null +++ b/www/modules/civicrm/CRM/Core/Smarty/plugins/modifier.crmEscapeSingleQuotes.php @@ -0,0 +1,22 @@ +assign_var` to * `$smarty->assignVar()` - * 2) if someone adds the Smarty3 package onto their site and - * defines CIVICRM_SMARTY3_AUTOLOAD_PATH then Smarty3 will load from that + * 2) if someone defines CIVICRM_SMARTY_AUTOLOAD_PATH then Smarty will load from that * location. * - * Note that experimenting with `CIVICRM_SMARTY3_AUTOLOAD_PATH` will not + * Note that experimenting with `CIVICRM_SMARTY_AUTOLOAD_PATH` will not * go well if extensions are installed that have not run civix upgrade * somewhat recently (ie have the old version of the hook_civicrm_config * with reference to `$template =& CRM_Core_Smarty::singleton();` */ +/** + * Get the path to load Smarty. + * + * @return string|null + */ +function crm_smarty_compatibility_get_path() { + return CRM_Utils_Constant::value('CIVICRM_SMARTY_AUTOLOAD_PATH') ?: CRM_Utils_Constant::value('CIVICRM_SMARTY3_AUTOLOAD_PATH'); +} + /** * Fix for bug CRM-392. Not sure if this is the best fix or it will impact * other similar PEAR packages. doubt it */ if (!class_exists('Smarty')) { - if (defined('CIVICRM_SMARTY3_AUTOLOAD_PATH')) { - // @todo - this is experimental but it allows someone to - // get Smarty3 to load instead of Smarty2 if set. - // It is likely the final Smarty3 solution will look - // different but this makes testing possible without re-inventing - // it each time we try... - require_once CIVICRM_SMARTY3_AUTOLOAD_PATH; + $path = crm_smarty_compatibility_get_path(); + if ($path) { + // Specify the smarty version to load. + require_once $path; } else { require_once 'Smarty/Smarty.class.php'; @@ -55,14 +60,6 @@ */ class CRM_Core_SmartyCompatibility extends Smarty { - public function loadFilter($type, $name) { - if (method_exists(parent::class, 'load_filter')) { - parent::load_filter($type, $name); - return; - } - parent::loadFilter($type, $name); - } - /** * @deprecated * @@ -76,6 +73,7 @@ public function load_filter($type, $name) { parent::load_filter($type, $name); return; } + CRM_Core_Error::deprecatedWarning('loadFilter'); parent::loadFilter($type, $name); } @@ -92,22 +90,15 @@ public function register_modifier($modifier, $modifier_impl) { parent::register_modifier($modifier, $modifier_impl); return; } + CRM_Core_Error::deprecatedWarning('registerPlugin'); parent::registerPlugin('modifier', $modifier, $modifier_impl); } - public function registerPlugin($type, $name, $callback, $cacheable = TRUE, $cache_attr = NULL) { - if (method_exists(parent::class, 'registerPlugin')) { - parent::registerPlugin($type, $name, $callback, $cacheable = TRUE, $cache_attr = NULL); - return; - } - if ($type === 'modifier') { - parent::register_modifier($name, $callback); - } - } - /** * Registers a resource to fetch a template * + * @deprecated + * * @param string $type name of resource * @param array $functions array of functions to handle resource */ @@ -116,12 +107,19 @@ public function register_resource($type, $functions) { parent::register_resource($type, $functions); return; } + if ($type === 'string') { + // Not valid / required for Smarty3+ + return; + } + CRM_Core_Error::deprecatedWarning('registerResource'); parent::registerResource($type, $functions); } /** * Registers custom function to be used in templates * + * @deprecated + * * @param string $function the name of the template function * @param string $function_impl the name of the PHP function to register * @param bool $cacheable @@ -134,12 +132,15 @@ public function register_function($function, $function_impl, $cacheable = TRUE, parent::register_function($function, $function_impl, $cacheable = TRUE, $cache_attrs = NULL); return; } + CRM_Core_Error::deprecatedWarning('registerPlugin'); parent::registerPlugin('function', $function, $function, $cacheable, $cache_attrs); } /** * Returns an array containing template variables * + * @deprecated since 5.69 will be removed around 5.79 + * * @param string $name * * @return array @@ -152,40 +153,19 @@ public function &get_template_vars($name = NULL) { return $var; } - /** - * Returns a single or all template variables - * - * @api Smarty::getTemplateVars() - * @link http://www.smarty.net/docs/en/api.get.template.vars.tpl - * - * @param string $varName variable name or NULL - * @param \Smarty_Internal_Data|\Smarty_Internal_Template|\Smarty $_ptr optional pointer to data object - * @param bool $searchParents include parent templates? - * - * @return mixed variable value or or array of variables - */ - public function getTemplateVars($varName = NULL, Smarty_Internal_Data $_ptr = NULL, $searchParents = TRUE) { - if (method_exists(parent::class, 'getTemplateVars')) { - return parent::getTemplateVars($varName, $_ptr, $searchParents); - } - return parent::get_template_vars($varName); - } - /** * Generally Civi mis-uses this for perceived php4 conformance, avoid. * * @deprecated * @param string $tpl_var * @param mixed $value - * - * @return mixed|null|void */ public function assign_by_ref($tpl_var, &$value) { if (method_exists(parent::class, 'assign_by_ref')) { parent::assign_by_ref($tpl_var, $value); return; } - return parent::assignByRef($tpl_var, $value); + $this->assign($tpl_var, $value); } /** @@ -219,19 +199,4 @@ public function template_exists($tpl_file) { return parent::templateExists($tpl_file); } - /** - * Check if a template resource exists - * - * @param string $resource_name template name - * - * @return bool status - * @throws \SmartyException - */ - public function templateExists($resource_name) { - if (method_exists(parent::class, 'templateExists')) { - return parent::templateExists($resource_name); - } - return parent::template_exists($resource_name); - } - } diff --git a/www/modules/civicrm/CRM/Core/WorkflowMessage/Profile/Profile.php b/www/modules/civicrm/CRM/Core/WorkflowMessage/Profile/Profile.php new file mode 100644 index 000000000..bd5427278 --- /dev/null +++ b/www/modules/civicrm/CRM/Core/WorkflowMessage/Profile/Profile.php @@ -0,0 +1,74 @@ + 'workflow/' . $workflow . '/general', + 'title' => ts('Profile Notification'), + 'tags' => ['preview'], + 'workflow' => $workflow, + ]; + } + } + + /** + * Build an example to use when rendering the workflow. + * + * @param array $example + * + * @throws \CRM_Core_Exception + */ + public function build(array &$example): void { + $workFlow = WorkflowMessage::get(TRUE)->addWhere('name', '=', $example['workflow'])->execute()->first(); + $this->setWorkflowName($workFlow['name']); + $messageTemplate = new $workFlow['class'](); + $this->addExampleData($messageTemplate, $example); + $example['data'] = $this->toArray($messageTemplate); + } + + /** + * Add relevant example data. + * + * @param \CRM_Core_WorkflowMessage_UFNotify $messageTemplate + * @param array $example + * + * @throws \CRM_Core_Exception + * @throws \Civi\API\Exception\UnauthorizedException + */ + private function addExampleData(\CRM_Core_WorkflowMessage_UFNotify $messageTemplate, $example): void { + $contact = Test::example('entity/Contact/Barb'); + $messageTemplate->setContact($contact); + $profile = UFGroup::get(FALSE)->setLimit(1)->execute()->first(); + $fields = UFField::get(FALSE)->addWhere('id', '=', $profile['id'])->execute(); + $values = []; + foreach ($fields as $field) { + if (isset($contact[$field['field_name']])) { + $values[$field['label']] = $contact[$field['field_name']]; + } + else { + $values[$field['label']] = ts('User entered field'); + } + } + $messageTemplate->setProfileID($profile['id']); + $messageTemplate->setProfileFields($values); + } + +} diff --git a/www/modules/civicrm/CRM/Core/WorkflowMessage/UFNotify.php b/www/modules/civicrm/CRM/Core/WorkflowMessage/UFNotify.php new file mode 100644 index 000000000..7ae0fb13a --- /dev/null +++ b/www/modules/civicrm/CRM/Core/WorkflowMessage/UFNotify.php @@ -0,0 +1,86 @@ +getContactID(), + TRUE, NULL, FALSE, FALSE, TRUE + ); + } + + /** + * @var string + * + * @scope tplParams as grouptitle + */ + protected $groupTitle; + + public function getGroupTitle(): string { + return CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', $this->getProfileID(), 'frontend_title'); + } + + /** + * @var string + * + * @scope tplParams + */ + protected $userDisplayName; + + public function getUserDisplayName(): string { + $loggedInUser = CRM_Core_Session::getLoggedInContactID(); + if (!$loggedInUser) { + return ''; + } + return CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', + $loggedInUser, + 'display_name' + ); + } + +} diff --git a/www/modules/civicrm/CRM/Core/xml/Menu/Admin.xml b/www/modules/civicrm/CRM/Core/xml/Menu/Admin.xml index 7a8f4e009..e6db4eb28 100644 --- a/www/modules/civicrm/CRM/Core/xml/Menu/Admin.xml +++ b/www/modules/civicrm/CRM/Core/xml/Menu/Admin.xml @@ -234,8 +234,6 @@ civicrm/admin/setting/preferences/date/edit Date Preferences CRM_Admin_Form_PreferencesDate - Customize Data and Screens - 97 civicrm/admin/menu @@ -599,8 +597,10 @@ civicrm/admin/setting/preferences/date - View Date Preferences + Date Preferences CRM_Admin_Page_PreferencesDate + Customize Data and Screens + 97 diff --git a/www/modules/civicrm/CRM/Core/xml/Menu/Contact.xml b/www/modules/civicrm/CRM/Core/xml/Menu/Contact.xml index 5de7c2814..d1117bbdc 100644 --- a/www/modules/civicrm/CRM/Core/xml/Menu/Contact.xml +++ b/www/modules/civicrm/CRM/Core/xml/Menu/Contact.xml @@ -238,11 +238,6 @@ CRM_Contact_Page_AJAX::groupTree access CiviCRM - - civicrm/ajax/custom - CRM_Contact_Page_AJAX::customField - access CiviCRM - civicrm/ajax/customvalue CRM_Contact_Page_AJAX::deleteCustomValue @@ -259,11 +254,6 @@ CRM_Contact_Page_AJAX::getContactEmail 1 - - civicrm/ajax/checkphone - CRM_Contact_Page_AJAX::getContactPhone - 1 - civicrm/ajax/subtype CRM_Contact_Page_AJAX::buildSubTypes diff --git a/www/modules/civicrm/CRM/Core/xml/Menu/Import.xml b/www/modules/civicrm/CRM/Core/xml/Menu/Import.xml index da0202ec7..03b15699b 100644 --- a/www/modules/civicrm/CRM/Core/xml/Menu/Import.xml +++ b/www/modules/civicrm/CRM/Core/xml/Menu/Import.xml @@ -6,7 +6,7 @@ Import import contacts,access CiviCRM 1 - CRM_Contact_Import_Controller + CRM_Import_Controller 400 @@ -14,7 +14,7 @@ Import Contacts import contacts,access CiviCRM 1 - CRM_Contact_Import_Controller + CRM_Import_Controller 410 @@ -36,13 +36,13 @@ Import Activities import contacts,access CiviCRM 1 - CRM_Activity_Import_Controller + CRM_Import_Controller 420 civicrm/import/contribution Import Contributions - CRM_Contribute_Import_Controller + CRM_Import_Controller access CiviContribute,edit contributions 1 520 @@ -53,7 +53,8 @@ Import Multi-value Custom Data access CiviCRM 1 - CRM_Custom_Import_Controller + CRM_Import_Controller + class_prefix=CRM_Custom_Import 420 diff --git a/www/modules/civicrm/CRM/Custom/Form/CustomData.php b/www/modules/civicrm/CRM/Custom/Form/CustomData.php index 94b9fb418..98c1781a6 100644 --- a/www/modules/civicrm/CRM/Custom/Form/CustomData.php +++ b/www/modules/civicrm/CRM/Custom/Form/CustomData.php @@ -16,18 +16,15 @@ */ /** - * this class builds custom data + * Deprecated class no longer used in core. + * + * @deprecated since 5.72 will be removed around 5.92 */ class CRM_Custom_Form_CustomData { /** * Generic wrapper to add custom data to a form via a single line in preProcess. * - * $this->getDefaultEntity() must be defined for the form class for this to work. - * - * If the postProcess form cannot use the api & instead uses a BAO function it will need. - * $params['custom'] = CRM_Core_BAO_CustomField::postProcess($submitted, $this->_id, $this->getDefaultEntity()); - * * @param CRM_Core_Form $form * @param null|string $entitySubType values stored in civicrm_custom_group.extends_entity_column_value * e.g Student for contact type @@ -36,6 +33,23 @@ class CRM_Custom_Form_CustomData { * @param null|int $contact_id contact ID associated with the custom data. * * @throws \CRM_Core_Exception + * @deprecated - preferred code now is to add to ensure the tpl loads custom + * data using the ajax template & add code to `buildForm()` like this + * + * ``` + * if ($this->isSubmitted()) { + * $this->addCustomDataFieldsToForm('Membership', array_filter([ + * 'id' => $this->getMembershipID(), + * 'membership_type_id' => $this->getSubmittedValue('membership_type_id') + * ])); + * } + * ``` + * + * $this->getDefaultEntity() must be defined for the form class for this to work. + * + * If the postProcess form cannot use the api & instead uses a BAO function it will need. + * $params['custom'] = CRM_Core_BAO_CustomField::postProcess($submitted, $this->_id, $this->getDefaultEntity()); + * */ public static function addToForm(&$form, $entitySubType = NULL, $subName = NULL, $groupCount = 1, $contact_id = NULL) { $entityName = $form->getDefaultEntity(); @@ -58,11 +72,6 @@ public static function addToForm(&$form, $entitySubType = NULL, $subName = NULL, self::setDefaultValues($form); } } - // need to assign custom data type and subtype to the template - $form->assign('customDataType', $entityName); - $form->assign('customDataSubType', $entitySubType); - $form->assign('entityID', $entityID); - $form->assign('cid', $contact_id); } /** @@ -78,6 +87,15 @@ public static function addToForm(&$form, $entitySubType = NULL, $subName = NULL, * @param bool $isLoadFromCache * * @throws \CRM_Core_Exception + * + * @deprecated see https://github.com/civicrm/civicrm-core/pull/29241 for preferred approach - basically + * 1) at the tpl layer use CRM/common/customDataBlock.tpl + * 2) to make the fields available for postProcess + * if ($this->isSubmitted()) { + * $this->addCustomDataFieldsToForm('FinancialAccount'); + * } + * 3) pass getSubmittedValues() to CRM_Core_BAO_CustomField::postProcess($this->getSubmittedValues(), $this->_id, 'FinancialAccount'); + * to ensure any money or number fields are handled for localisation */ public static function preProcess( &$form, $extendsEntityColumn = NULL, $subType = NULL, diff --git a/www/modules/civicrm/CRM/Custom/Form/CustomDataTrait.php b/www/modules/civicrm/CRM/Custom/Form/CustomDataTrait.php index b3d9bbc7f..f17fb922f 100644 --- a/www/modules/civicrm/CRM/Custom/Form/CustomDataTrait.php +++ b/www/modules/civicrm/CRM/Custom/Form/CustomDataTrait.php @@ -1 +1,151 @@ getSubmittedValues()`. + * + * @internal this is not supported for use outside of core and there is no guarantee the + * function signature or behaviour won't change. It you use if from outside core + * be sure to use unit tests in your non-core use. + * + * @param string $entity + * @param array $filters + * Filters is only needed for entities where CustomDataGroups may be filtered. + * e.g Activity custom data groups might be available for only some entity types. + * In that case the filters would hold the id (if any) of the entity and the + * activity_type_id if known. + * + * @throws \CRM_Core_Exception + */ + protected function addCustomDataFieldsToForm(string $entity, array $filters = []): void { + $fields = (array) civicrm_api4($entity, 'getFields', [ + 'action' => 'create', + 'values' => $filters, + 'where' => [ + ['type', '=', 'Custom'], + ['readonly', '=', FALSE], + ], + 'checkPermissions' => TRUE, + ])->indexBy('custom_field_id'); + $fieldFilters = ['style' => 'Inline']; + if ($entity === 'Contact') { + // Ideally this would not be contact specific but the function being + // called here does not handle the filters as received. + $fieldFilters += [ + 'extends' => [$entity, $filters['contact_type']], + 'is_multiple' => TRUE, + ]; + if (!empty($filters['contact_sub_type'])) { + $fieldFilters['extends_entity_column_value'] = [NULL, $filters['contact_sub_type']]; + } + + $multipleCustomGroups = CRM_Core_BAO_CustomGroup::getAll($fieldFilters); + foreach ($multipleCustomGroups as $multipleCustomGroup) { + foreach ($multipleCustomGroup['fields'] as $groupField) { + $groupField['custom_group_id.is_multiple'] = TRUE; + $groupField['table_name'] = $multipleCustomGroup['table_name']; + $groupField['custom_field_id'] = $groupField['id']; + $groupField['required'] = $groupField['is_required']; + $groupField['input_type'] = $groupField['html_type']; + $fields[$groupField['id']] = $groupField; + } + } + } + + $formValues = []; + foreach ($fields as $field) { + // Here we add the custom fields to the form + // based on whether they have been 'POSTed' + foreach ($this->getInstancesOfField($field['custom_field_id']) as $elementName) { + $formValues[$elementName] = $this->addCustomField($elementName, $field); + } + } + $qf = $this->get('qfKey'); + $this->assign('qfKey', $qf); + // We cached the POSTed values so that they can be reloaded + // if the form fails to submit. Note that we may be combining the + // values with those stored by other custom field entities on the + // form. + $defaultValues = (array) Civi::cache('customData')->get($qf); + Civi::cache('customData')->set($qf, $formValues + $defaultValues); + } + + /** + * Get the instances of the given field in $_POST to determine how many to add to the form. + * + * @param int $id + * + * @return array + */ + private function getInstancesOfField($id): array { + $instances = []; + foreach (array_merge($_POST, ($_FILES ?? [])) as $key => $value) { + if (preg_match('/^custom_' . $id . '_?(-?\d+)?$/', $key)) { + $instances[] = $key; + } + } + return $instances; + } + + /** + * Add the given field to the form. + * + * @param string $elementName + * @param array $field + * + * @return mixed + * + * @throws \CRM_Core_Exception + * + * @internal this is not supported for use outside of core and there is no guarantee the + * function signature or behaviour won't change. It you use if from outside core + * be sure to use unit tests in your non-core use. + */ + protected function addCustomField(string $elementName, array $field) { + // Note that passing required = TRUE here does not seem to actually do anything. As long + // as the form opens in pop up mode jquery validation from the ajax form ensures required fields are + // submitted. In non-popup mode however the required is not enforced. This appears to be + // a bug that has been around for a while. + CRM_Core_BAO_CustomField::addQuickFormElement($this, $elementName, $field['custom_field_id'], $field['required']); + if ($field['input_type'] === 'File') { + $this->registerFileField([$elementName]); + } + // Get any values from the POST & cache them so that they can be retrieved from the + // CustomDataByType form in it's setDefaultValues() function - otherwise it cannot reload the + // values that were just entered if validation fails. + return is_string($this->getSubmitValue($elementName)) ? CRM_Utils_String::purifyHTML($this->getSubmitValue($elementName)) : $this->getSubmitValue($elementName); + } + +} diff --git a/www/modules/civicrm/CRM/Custom/Form/Field.php b/www/modules/civicrm/CRM/Custom/Form/Field.php index ece46e555..10351a9bb 100644 --- a/www/modules/civicrm/CRM/Custom/Form/Field.php +++ b/www/modules/civicrm/CRM/Custom/Form/Field.php @@ -170,6 +170,7 @@ public function setDefaultValues() { $defaults['note_columns'] = 60; $defaults['note_rows'] = 4; $defaults['is_view'] = 0; + $defaults['fk_entity_on_delete'] = CRM_Core_DAO_CustomField::fields()['fk_entity_on_delete']['default']; if (CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $this->_gid, 'is_multiple')) { $defaults['in_selector'] = 1; @@ -193,7 +194,7 @@ public function setDefaultValues() { */ public function buildQuickForm() { if ($this->_gid) { - $this->_title = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $this->_gid, 'title'); + $this->_title = CRM_Core_BAO_CustomGroup::getGroup(['id' => $this->_gid])['title']; $this->setTitle($this->_title . ' - ' . ($this->_id ? ts('Edit Field') : ts('New Field'))); } $this->assign('gid', $this->_gid); @@ -238,6 +239,8 @@ public function buildQuickForm() { 'select' => ['minimumInputLength' => 0], ]); + $this->addField('fk_entity_on_delete'); + $isUpdateAction = $this->_action == CRM_Core_Action::UPDATE; if ($isUpdateAction) { $this->freeze('data_type'); @@ -826,7 +829,7 @@ public static function formRule($fields, $files, $self) { $options = array_map(['CRM_Core_DAO', 'escapeString'], array_filter($fields['option_value'], 'strlen')); $optionQuery = '"' . implode('","', $options) . '"'; } - $table = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $self->_gid, 'table_name'); + $table = CRM_Core_BAO_CustomGroup::getGroup(['id' => $self->_gid])['table_name']; $column = $self->_values['column_name']; $invalid = CRM_Core_DAO::singleValueQuery("SELECT COUNT(*) FROM `$table` WHERE `$column` NOT IN ($optionQuery)"); if ($invalid) { @@ -944,7 +947,7 @@ public static function clearEmptyOptions(&$fields) { * @throws CRM_Core_Exception */ public function getMultiValueCount() { - $table = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $this->_gid, 'table_name'); + $table = CRM_Core_BAO_CustomGroup::getGroup(['id' => $this->_gid])['table_name']; $column = $this->_values['column_name']; $sp = CRM_Core_DAO::VALUE_SEPARATOR; $sql = "SELECT COUNT(*) FROM `$table` WHERE `$column` LIKE '{$sp}%{$sp}%{$sp}'"; diff --git a/www/modules/civicrm/CRM/Custom/Import/Controller.php b/www/modules/civicrm/CRM/Custom/Import/Controller.php deleted file mode 100644 index 0f740f59a..000000000 --- a/www/modules/civicrm/CRM/Custom/Import/Controller.php +++ /dev/null @@ -1,30 +0,0 @@ -_stateMachine = new CRM_Import_StateMachine($this, $action); - - // create and instantiate the pages - $this->addPages($this->_stateMachine, $action); - - // add all the actions - $config = CRM_Core_Config::singleton(); - $this->addActions($config->uploadDir, ['uploadFile']); - } - -} diff --git a/www/modules/civicrm/CRM/Custom/Import/Parser/Api.php b/www/modules/civicrm/CRM/Custom/Import/Parser/Api.php index 8ccef384a..da5083db0 100644 --- a/www/modules/civicrm/CRM/Custom/Import/Parser/Api.php +++ b/www/modules/civicrm/CRM/Custom/Import/Parser/Api.php @@ -1,15 +1,10 @@ getMappedRow($values); - $formatted = []; - foreach ($params as $key => $value) { - if ($value !== '') { - $formatted[$key] = $value; - } - } - - if (isset($params['external_identifier']) && !isset($params['contact_id'])) { - $checkCid = new CRM_Contact_DAO_Contact(); - $checkCid->external_identifier = $params['external_identifier']; - $checkCid->find(TRUE); - $formatted['id'] = $checkCid->id; - } - else { - $formatted['id'] = $params['contact_id']; - } - - $this->formatCommonData($params, $formatted); - foreach ($formatted['custom'] as $key => $val) { - $params['custom_' . $key] = $val[-1]['value']; - } $params['skipRecentView'] = TRUE; $params['check_permissions'] = TRUE; - $params['entity_id'] = $formatted['id']; - civicrm_api3('custom_value', 'create', $params); - $this->setImportStatus($rowNumber, 'IMPORTED', '', $formatted['id']); + $params['entity_id'] = $params['contact_id']; + civicrm_api3('CustomValue', 'create', $params); + $this->setImportStatus($rowNumber, 'IMPORTED', '', $params['contact_id']); } catch (CRM_Core_Exception $e) { - $this->setImportStatus($rowNumber, 'ERROR', $e->getMessage(), $formatted['id']); + $this->setImportStatus($rowNumber, 'ERROR', $e->getMessage(), $params['contact_id'] ?? NULL); } } @@ -78,7 +52,7 @@ public function import($values) { */ public function setFieldMetadata(): void { if (!$this->importableFieldsMetadata) { - $customGroupID = $this->getSubmittedValue('multipleCustomData'); + $customGroupID = $this->getCustomGroupID(); $importableFields = $this->getGroupFieldsForImport($customGroupID); $this->importableFieldsMetadata = array_merge([ 'do_not_import' => ['title' => ts('- do not import -')], @@ -97,77 +71,6 @@ public function getRequiredFields(): array { return [['contact_id'], ['external_identifier']]; } - /** - * Adapted from CRM_Contact_Import_Parser_Contact::formatCommonData - * - * TODO: Is this function even necessary? All values get passed to the api anyway. - * - * @param array $params - * Contain record values. - * @param array $formatted - * Array of formatted data. - */ - private function formatCommonData($params, &$formatted) { - - $customFields = CRM_Core_BAO_CustomField::getFields(NULL); - - //now format custom data. - foreach ($params as $key => $field) { - - if ($key == 'id' && isset($field)) { - $formatted[$key] = $field; - } - - //Handling Custom Data - if (($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) && - array_key_exists($customFieldID, $customFields) - ) { - - $extends = $customFields[$customFieldID]['extends'] ?? NULL; - $htmlType = $customFields[$customFieldID]['html_type'] ?? NULL; - $dataType = $customFields[$customFieldID]['data_type'] ?? NULL; - $serialized = CRM_Core_BAO_CustomField::isSerialized($customFields[$customFieldID]); - - if (!$serialized && in_array($htmlType, ['Select', 'Radio', 'Autocomplete-Select']) && in_array($dataType, ['String', 'Int'])) { - $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE); - foreach ($customOption as $customValue) { - $val = $customValue['value'] ?? NULL; - $label = strtolower($customValue['label'] ?? ''); - $value = strtolower(trim($formatted[$key])); - if (($value == $label) || ($value == strtolower($val))) { - $params[$key] = $formatted[$key] = $val; - } - } - } - elseif ($serialized && !empty($formatted[$key]) && !empty($params[$key])) { - $mulValues = explode(',', $formatted[$key]); - $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE); - $formatted[$key] = []; - $params[$key] = []; - foreach ($mulValues as $v1) { - foreach ($customOption as $v2) { - if ((strtolower($v2['label']) == strtolower(trim($v1))) || - (strtolower($v2['value']) == strtolower(trim($v1))) - ) { - if ($htmlType === 'CheckBox') { - $params[$key][$v2['value']] = $formatted[$key][$v2['value']] = 1; - } - else { - $params[$key][] = $formatted[$key][] = $v2['value']; - } - } - } - } - } - } - } - - if (!empty($key) && ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) && array_key_exists($customFieldID, $customFields)) { - // @todo calling api functions directly is not supported - _civicrm_api3_custom_format_params($params, $formatted, $extends); - } - } - /** * Return the field ids and names (with groups) for import purpose. * @@ -175,44 +78,38 @@ private function formatCommonData($params, &$formatted) { * Custom group ID. * * @return array - * - * @noinspection PhpDocMissingThrowsInspection - * @noinspection PhpUnhandledExceptionInspection */ private function getGroupFieldsForImport(int $customGroupID): array { $importableFields = []; - $fields = (array) CustomField::get(FALSE) - ->addSelect('*', 'custom_group_id.is_multiple', 'custom_group_id.name', 'custom_group_id.extends') - ->addWhere('custom_group_id', '=', $customGroupID)->execute(); + $customGroup = CRM_Core_BAO_CustomGroup::getGroup(['id' => $customGroupID]); - foreach ($fields as $values) { - $datatype = $values['data_type'] ?? NULL; - if ($datatype === 'File') { + foreach ($customGroup['fields'] as $values) { + if ($values['data_type'] === 'File') { continue; } /* generate the key for the fields array */ $key = 'custom_' . $values['id']; $regexp = preg_replace('/[.,;:!?]/', '', $values['label']); - $importableFields[$key] = [ + $importableFields[$key] = array_merge($values, [ 'name' => $key, 'title' => $values['label'] ?? NULL, 'headerPattern' => '/' . preg_quote($regexp, '/') . '/i', 'import' => 1, 'custom_field_id' => $values['id'], - 'options_per_line' => $values['options_per_line'], - 'data_type' => $values['data_type'], - 'html_type' => $values['html_type'], 'type' => CRM_Core_BAO_CustomField::dataToType()[$values['data_type']], - 'is_search_range' => $values['is_search_range'], - 'date_format' => $values['date_format'], - 'time_format' => $values['time_format'], - 'extends' => $values['custom_group_id.extends'], - 'custom_group_id' => $customGroupID, - 'custom_group_id.name' => $values['custom_group_id.name'], - 'is_multiple' => $values['custom_group_id.is_multiple'], - ]; + 'extends' => $customGroup['extends'], + 'custom_group_id.name' => $customGroup['name'], + 'is_multiple' => $customGroup['is_multiple'], + ]); } return $importableFields; } + /** + * @return int + */ + private function getCustomGroupID(): int { + return (int) $this->getSubmittedValue('multipleCustomData'); + } + } diff --git a/www/modules/civicrm/CRM/Cxn/DAO/Cxn.php b/www/modules/civicrm/CRM/Cxn/DAO/Cxn.php index fda0b6350..4bdcfc531 100644 --- a/www/modules/civicrm/CRM/Cxn/DAO/Cxn.php +++ b/www/modules/civicrm/CRM/Cxn/DAO/Cxn.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Cxn/Cxn.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:8bca70c8807aff7f6421ddc9502e4c34) + * (GenCodeChecksum:6d34eafc9c6144dd8d0a262cb6831f40) */ /** diff --git a/www/modules/civicrm/CRM/Dedupe/BAO/DedupeRule.php b/www/modules/civicrm/CRM/Dedupe/BAO/DedupeRule.php index 368a35b50..8cf25614e 100644 --- a/www/modules/civicrm/CRM/Dedupe/BAO/DedupeRule.php +++ b/www/modules/civicrm/CRM/Dedupe/BAO/DedupeRule.php @@ -21,32 +21,30 @@ */ class CRM_Dedupe_BAO_DedupeRule extends CRM_Dedupe_DAO_DedupeRule { - /** - * Ids of the contacts to limit the SQL queries (whole-database queries otherwise) - * @var array - */ - public $contactIds = []; - - /** - * Params to dedupe against (queries against the whole contact set otherwise) - * @var array - */ - public $params = []; - /** * Return the SQL query for the given rule - either for finding matching * pairs of contacts, or for matching against the $params variable (if set). * + * @param array|null $params + * Params to dedupe against (queries against the whole contact set otherwise) + * @param array $contactIDs + * Ids of the contacts to limit the SQL queries (whole-database queries otherwise) + * @param array $rule + * * @return string * SQL query performing the search * or NULL if params is present and doesn't have and for a field. * * @throws \CRM_Core_Exception + * + * @deprecated since 5.73 will be removed around 5.80 + * @internal do not call from outside tested core code. No universe uses Feb 2024. */ - public function sql() { - if ($this->params && - (!array_key_exists($this->rule_table, $this->params) || - !array_key_exists($this->rule_field, $this->params[$this->rule_table]) + public static function sql($params, $contactIDs, array $rule): ?string { + CRM_Core_Error::deprecatedFunctionWarning('unsed, no alternative'); + if ($params && + (!array_key_exists($rule['rule_table'], $params) || + !array_key_exists($rule['rule_field'], $params[$rule['rule_table']]) ) ) { // if params is present and doesn't have an entry for a field, don't construct the clause. @@ -58,29 +56,29 @@ public function sql() { // $using are arrays of required field matchings (for substring and // full matches, respectively) $where = []; - $on = ["SUBSTR(t1.{$this->rule_field}, 1, {$this->rule_length}) = SUBSTR(t2.{$this->rule_field}, 1, {$this->rule_length})"]; + $on = ["SUBSTR(t1.{$rule['rule_field']}, 1, {$rule['rule_length']}) = SUBSTR(t2.{$rule['rule_field']}, 1, {$rule['rule_length']})"]; $innerJoinClauses = [ - "t1.{$this->rule_field} IS NOT NULL", - "t2.{$this->rule_field} IS NOT NULL", - "t1.{$this->rule_field} = t2.{$this->rule_field}", + "t1.{$rule['rule_field']} IS NOT NULL", + "t2.{$rule['rule_field']} IS NOT NULL", + "t1.{$rule['rule_field']} = t2.{$rule['rule_field']}", ]; - if (in_array($this->getFieldType($this->rule_field), CRM_Utils_Type::getTextTypes(), TRUE)) { - $innerJoinClauses[] = "t1.{$this->rule_field} <> ''"; - $innerJoinClauses[] = "t2.{$this->rule_field} <> ''"; + if (in_array(CRM_Dedupe_BAO_DedupeRule::getFieldType($rule['rule_field'], $rule['rule_table']), CRM_Utils_Type::getTextTypes(), TRUE)) { + $innerJoinClauses[] = "t1.{$rule['rule_field']} <> ''"; + $innerJoinClauses[] = "t2.{$rule['rule_field']} <> ''"; } $cidRefs = CRM_Core_DAO::getReferencesToContactTable(); $eidRefs = CRM_Core_DAO::getDynamicReferencesToTable('civicrm_contact'); - switch ($this->rule_table) { + switch ($rule['rule_table']) { case 'civicrm_contact': $id = 'id'; //we should restrict by contact type in the first step - $sql = "SELECT contact_type FROM civicrm_dedupe_rule_group WHERE id = {$this->dedupe_rule_group_id};"; + $sql = "SELECT contact_type FROM civicrm_dedupe_rule_group WHERE id = {$rule['dedupe_rule_group_id']};"; $ct = CRM_Core_DAO::singleValueQuery($sql); - if ($this->params) { + if ($params) { $where[] = "t1.contact_type = '{$ct}'"; } else { @@ -90,10 +88,10 @@ public function sql() { break; default: - if (array_key_exists($this->rule_table, $eidRefs)) { - $id = $eidRefs[$this->rule_table][0]; - $entity_table = $eidRefs[$this->rule_table][1]; - if ($this->params) { + if (array_key_exists($rule['rule_table'], $eidRefs)) { + $id = $eidRefs[$rule['rule_table']][0]; + $entity_table = $eidRefs[$rule['rule_table']][1]; + if ($params) { $where[] = "t1.$entity_table = 'civicrm_contact'"; } else { @@ -101,59 +99,59 @@ public function sql() { $where[] = "t2.$entity_table = 'civicrm_contact'"; } } - elseif (array_key_exists($this->rule_table, $cidRefs)) { - $id = $cidRefs[$this->rule_table][0]; + elseif (array_key_exists($rule['rule_table'], $cidRefs)) { + $id = $cidRefs[$rule['rule_table']][0]; } else { - throw new CRM_Core_Exception("Unsupported rule_table for civicrm_dedupe_rule.id of {$this->id}"); + throw new CRM_Core_Exception("Unsupported rule_table for civicrm_dedupe_rule.id of {$rule['id']}"); } break; } // build SELECT based on the field names containing contact ids // if there are params provided, id1 should be 0 - if ($this->params) { - $select = "t1.$id id1, {$this->rule_weight} weight"; + if ($params) { + $select = "t1.$id id1, {$rule['rule_weight']} weight"; $subSelect = 'id1, weight'; } else { - $select = "t1.$id id1, t2.$id id2, {$this->rule_weight} weight"; + $select = "t1.$id id1, t2.$id id2, {$rule['rule_weight']} weight"; $subSelect = 'id1, id2, weight'; } // build FROM (and WHERE, if it's a parametrised search) // based on whether the rule is about substrings or not - if ($this->params) { - $from = "{$this->rule_table} t1"; + if ($params) { + $from = "{$rule['rule_table']} t1"; $str = 'NULL'; - if (isset($this->params[$this->rule_table][$this->rule_field])) { - $str = trim(CRM_Utils_Type::escape($this->params[$this->rule_table][$this->rule_field], 'String')); + if (isset($params[$rule['rule_table']][$rule['rule_field']])) { + $str = trim(CRM_Utils_Type::escape($params[$rule['rule_table']][$rule['rule_field']], 'String')); } - if ($this->rule_length) { - $where[] = "SUBSTR(t1.{$this->rule_field}, 1, {$this->rule_length}) = SUBSTR('$str', 1, {$this->rule_length})"; - $where[] = "t1.{$this->rule_field} IS NOT NULL"; + if ($rule['rule_length']) { + $where[] = "SUBSTR(t1.{$rule['rule_field']}, 1, {$rule['rule_length']}) = SUBSTR('$str', 1, {$rule['rule_length']})"; + $where[] = "t1.{$rule['rule_field']} IS NOT NULL"; } else { - $where[] = "t1.{$this->rule_field} = '$str'"; + $where[] = "t1.{$rule['rule_field']} = '$str'"; } } else { - if ($this->rule_length) { - $from = "{$this->rule_table} t1 JOIN {$this->rule_table} t2 ON (" . implode(' AND ', $on) . ")"; + if ($rule['rule_length']) { + $from = "{$rule['rule_table']} t1 INNER JOIN {$rule['rule_table']} t2 ON (" . implode(' AND ', $on) . ")"; } else { - $from = "{$this->rule_table} t1 INNER JOIN {$this->rule_table} t2 ON (" . implode(' AND ', $innerJoinClauses) . ")"; + $from = "{$rule['rule_table']} t1 INNER JOIN {$rule['rule_table']} t2 ON (" . implode(' AND ', $innerJoinClauses) . ")"; } } // finish building WHERE, also limit the results if requested - if (!$this->params) { + if (!$params) { $where[] = "t1.$id < t2.$id"; } $query = "SELECT $select FROM $from WHERE " . implode(' AND ', $where); - if ($this->contactIds) { + if ($contactIDs) { $cids = []; - foreach ($this->contactIds as $cid) { + foreach ($contactIDs as $cid) { $cids[] = CRM_Utils_Type::escape($cid, 'Integer'); } if (count($cids) == 1) { @@ -178,8 +176,10 @@ public function sql() { * * @return array * rule fields array associated to rule group + * + * @internal do not call from outside tested core code. No universe uses Feb 2024. */ - public static function dedupeRuleFields($params) { + public static function dedupeRuleFields(array $params) { $rgBao = new CRM_Dedupe_BAO_DedupeRuleGroup(); $rgBao->used = $params['used']; $rgBao->contact_type = $params['contact_type']; @@ -191,7 +191,7 @@ public static function dedupeRuleFields($params) { $ruleFields = []; while ($ruleBao->fetch()) { $field_name = $ruleBao->rule_field; - if ($field_name == 'phone_numeric') { + if ($field_name === 'phone_numeric') { $field_name = 'phone'; } $ruleFields[] = $field_name; @@ -204,6 +204,8 @@ public static function dedupeRuleFields($params) { * @param int $oid * * @return bool + * + * @internal do not call from outside tested core code. No universe uses Feb 2024. */ public static function validateContacts($cid, $oid) { if (!$cid || !$oid) { @@ -225,19 +227,26 @@ public static function validateContacts($cid, $oid) { * Get the specification for the given field. * * @param string $fieldName + * @param string $ruleTable * * @return array * @throws \CRM_Core_Exception + * @internal function has only ever been available from the class & will be moved. */ - public function getFieldType($fieldName) { - $entity = CRM_Core_DAO_AllCoreTables::getEntityNameForTable($this->rule_table); + public static function getFieldType(string $fieldName, string $ruleTable) { + $entity = CRM_Core_DAO_AllCoreTables::getEntityNameForTable($ruleTable); if (!$entity) { // This means we have stored a custom field rather than an entity name in rule_table, figure out the entity. - $entity = civicrm_api3('CustomGroup', 'getvalue', ['table_name' => $this->rule_table, 'return' => 'extends']); + $customGroup = CRM_Core_BAO_CustomGroup::getGroup(['table_name' => $ruleTable]); + if (!$customGroup) { + throw new CRM_Core_Exception('Unknown dedupeRule field'); + } + $entity = $customGroup['extends']; if (in_array($entity, CRM_Contact_BAO_ContactType::basicTypes(TRUE), TRUE)) { $entity = 'Contact'; } - $fieldName = 'custom_' . civicrm_api3('CustomField', 'getvalue', ['column_name' => $fieldName, 'return' => 'id']); + $fieldIds = array_column($customGroup['fields'], 'id', 'column_name'); + $fieldName = 'custom_' . $fieldIds[$fieldName]; } $fields = civicrm_api3($entity, 'getfields', ['action' => 'create'])['values']; return $fields[$fieldName]['type']; diff --git a/www/modules/civicrm/CRM/Dedupe/BAO/DedupeRuleGroup.php b/www/modules/civicrm/CRM/Dedupe/BAO/DedupeRuleGroup.php index c45e90560..f4d698ccc 100644 --- a/www/modules/civicrm/CRM/Dedupe/BAO/DedupeRuleGroup.php +++ b/www/modules/civicrm/CRM/Dedupe/BAO/DedupeRuleGroup.php @@ -66,12 +66,6 @@ public static function supportedFields($requestedType): array { 'civicrm_country.name' => 'civicrm_address.country_id', 'civicrm_county.name' => 'civicrm_address.county_id', 'civicrm_state_province.name' => 'civicrm_address.state_province_id', - 'gender.label' => 'civicrm_contact.gender_id', - 'individual_prefix.label' => 'civicrm_contact.prefix_id', - 'individual_suffix.label' => 'civicrm_contact.suffix_id', - 'addressee.label' => 'civicrm_contact.addressee_id', - 'email_greeting.label' => 'civicrm_contact.email_greeting_id', - 'postal_greeting.label' => 'civicrm_contact.postal_greeting_id', 'civicrm_phone.phone' => 'civicrm_phone.phone_numeric', ]; // the table names we support in dedupe rules - a filter for importableFields() @@ -109,18 +103,19 @@ public static function supportedFields($requestedType): array { // exension is installed (https://github.com/eileenmcnaughton/org.wikimedia.thethe) $fields[$ctype]['civicrm_contact']['sort_name'] = ts('Sort Name'); + $customGroups = CRM_Core_BAO_CustomGroup::getAll([ + 'extends' => $ctype, + 'is_active' => TRUE, + ], CRM_Core_Permission::EDIT); // add all custom data fields including those only for sub_types. - foreach (CRM_Core_BAO_CustomGroup::getTree($ctype, NULL, NULL, -1, [], NULL, TRUE, NULL, TRUE) as $key => $cg) { - if (!is_int($key)) { - continue; - } + foreach ($customGroups as $cg) { foreach ($cg['fields'] as $cf) { $fields[$ctype][$cg['table_name']][$cf['column_name']] = $cg['title'] . ' : ' . $cf['label']; } } } //Does this have to run outside of cache? - CRM_Utils_Hook::dupeQuery(CRM_Core_DAO::$_nullObject, 'supportedFields', $fields); + CRM_Utils_Hook::dupeQuery(NULL, 'supportedFields', $fields); Civi::$statics[__CLASS__]['supportedFields'] = $fields; } @@ -145,16 +140,6 @@ private static function importableFields($contactType): array { $fields = CRM_Contact_DAO_Contact::import(); - // get the fields thar are meant for contact types - if (in_array($contactType, [ - 'Individual', - 'Household', - 'Organization', - 'All', - ])) { - $fields = array_merge($fields, CRM_Core_OptionValue::getFields('', $contactType)); - } - $locationFields = array_merge(CRM_Core_DAO_Address::import(), CRM_Core_DAO_Phone::import(), CRM_Core_DAO_Email::import(), @@ -220,12 +205,11 @@ private function tableQuery() { if (!$this->name == NULL || $this->is_reserved == NULL) { $this->find(TRUE); } + $contactType = $this->contact_type; // Reserved Rule Groups can optionally get special treatment by // implementing an optimization class and returning a query array. - if ($this->is_reserved && - CRM_Utils_File::isIncludable("CRM/Dedupe/BAO/QueryBuilder/{$this->name}.php") - ) { + if ($this->isUseReservedQuery()) { $command = empty($this->params) ? 'internal' : 'record'; $queries = call_user_func(["CRM_Dedupe_BAO_QueryBuilder_{$this->name}", $command], $this); } @@ -244,11 +228,15 @@ private function tableQuery() { // tailored to respect the param and contactId options provided. $queries = []; while ($bao->fetch()) { - $bao->contactIds = $this->contactIds; - $bao->params = $this->params; - // Skipping empty rules? Empty rules shouldn't exist; why check? - if ($query = $bao->sql()) { + if ($query = self::sql($this->params, $this->contactIds, [ + 'id' => (int) $bao->id, + 'rule_table' => $bao->rule_table, + 'rule_length' => $bao->rule_length, + 'rule_field' => $bao->rule_field, + 'rule_weight' => $bao->rule_weight, + 'dedupe_rule_group_id' => $bao->dedupe_rule_group_id, + ], $contactType)) { $queries["{$bao->rule_table}.{$bao->rule_field}.{$bao->rule_weight}"] = $query; } } @@ -264,7 +252,168 @@ private function tableQuery() { return $queries; } - public function fillTable() { + /** + * Return the SQL query for the given rule - either for finding matching + * pairs of contacts, or for matching against the $params variable (if set). + * + * @param array|null $params + * Params to dedupe against (queries against the whole contact set otherwise) + * @param array $contactIDs + * Ids of the contacts to limit the SQL queries (whole-database queries otherwise) + * @param array $rule + * @param string $contactType + * + * @return string + * SQL query performing the search + * or NULL if params is present and doesn't have and for a field. + * + * @throws \CRM_Core_Exception + * @internal do not call from outside tested core code. No universe uses Feb 2024. + * + */ + private static function sql($params, $contactIDs, array $rule, string $contactType): ?string { + if ($params && + (!array_key_exists($rule['rule_table'], $params) || + !array_key_exists($rule['rule_field'], $params[$rule['rule_table']]) + ) + ) { + // if params is present and doesn't have an entry for a field, don't construct the clause. + return NULL; + } + + $filter = self::getRuleTableFilter($rule['rule_table'], $contactType); + $contactIDFieldName = self::getContactIDFieldName($rule['rule_table']); + + // build FROM (and WHERE, if it's a parametrised search) + // based on whether the rule is about substrings or not + if ($params) { + $select = "t1.$contactIDFieldName id1, {$rule['rule_weight']} weight"; + $subSelect = 'id1, weight'; + $where = $filter ? ['t1.' . $filter] : []; + $from = "{$rule['rule_table']} t1"; + $str = 'NULL'; + if (isset($params[$rule['rule_table']][$rule['rule_field']])) { + $str = trim(CRM_Utils_Type::escape($params[$rule['rule_table']][$rule['rule_field']], 'String')); + } + if ($rule['rule_length']) { + $where[] = "SUBSTR(t1.{$rule['rule_field']}, 1, {$rule['rule_length']}) = SUBSTR('$str', 1, {$rule['rule_length']})"; + $where[] = "t1.{$rule['rule_field']} IS NOT NULL"; + } + else { + $where[] = "t1.{$rule['rule_field']} = '$str'"; + } + } + else { + $select = "t1.$contactIDFieldName id1, t2.$contactIDFieldName id2, {$rule['rule_weight']} weight"; + $subSelect = 'id1, id2, weight'; + $where = $filter ? [ + 't1.' . $filter, + 't2.' . $filter, + ] : []; + $where[] = "t1.$contactIDFieldName < t2.$contactIDFieldName"; + $from = "{$rule['rule_table']} t1 INNER JOIN {$rule['rule_table']} t2 ON (" . self::getRuleFieldFilter($rule) . ")"; + } + + $query = "SELECT $select FROM $from WHERE " . implode(' AND ', $where); + if ($contactIDs) { + $cids = []; + foreach ($contactIDs as $cid) { + $cids[] = CRM_Utils_Type::escape($cid, 'Integer'); + } + if (count($cids) == 1) { + $query .= " AND (t1.$contactIDFieldName = {$cids[0]}) UNION $query AND t2.$contactIDFieldName = {$cids[0]}"; + } + else { + $query .= " AND t1.$contactIDFieldName IN (" . implode(',', $cids) . ") + UNION $query AND t2.$contactIDFieldName IN (" . implode(',', $cids) . ")"; + } + // The `weight` is ambiguous in the context of the union; put the whole + // thing in a subquery. + $query = "SELECT $subSelect FROM ($query) subunion"; + } + + return $query; + } + + /** + * Get the name of the field in the table that refers to the Contact ID. + * + * e.g in civicrm_contact this is 'id' whereas in civicrm_address this is + * contact_id and in a custom field table it might be entity_id. + * + * @param string $tableName + * + * @return string + * Usually id, contact_id or entity_id. + * @throws \CRM_Core_Exception + */ + private static function getContactIDFieldName(string $tableName): string { + if ($tableName === 'civicrm_contact') { + return 'id'; + } + if (isset(CRM_Core_DAO::getDynamicReferencesToTable('civicrm_contact')[$tableName][0])) { + return CRM_Core_DAO::getDynamicReferencesToTable('civicrm_contact')[$tableName][0]; + } + if (isset(\CRM_Core_DAO::getReferencesToContactTable()[$tableName][0])) { + return \CRM_Core_DAO::getReferencesToContactTable()[$tableName][0]; + } + throw new CRM_Core_Exception('invalid field'); + } + + /** + * Get any where filter that restricts the specific table. + * + * Generally this is along the lines of entity_table = civicrm_contact + * although for the contact table it could be the id restriction. + * + * @param string $tableName + * @param string $contactType + * + * @return string + */ + private static function getRuleTableFilter(string $tableName, string $contactType): string { + if ($tableName === 'civicrm_contact') { + return "contact_type = '{$contactType}'"; + } + $dynamicReferences = CRM_Core_DAO::getDynamicReferencesToTable('civicrm_contact')[$tableName] ?? NULL; + if (!$dynamicReferences) { + return ''; + } + if (!empty(CRM_Core_DAO::getDynamicReferencesToTable('civicrm_contact')[$tableName])) { + return $dynamicReferences[1] . "= 'civicrm_contact'"; + } + return ''; + } + + /** + * @param array $rule + * + * @return string + * @throws \CRM_Core_Exception + */ + private static function getRuleFieldFilter(array $rule): string { + if ($rule['rule_length']) { + $on = ["SUBSTR(t1.{$rule['rule_field']}, 1, {$rule['rule_length']}) = SUBSTR(t2.{$rule['rule_field']}, 1, {$rule['rule_length']})"]; + return "(" . implode(' AND ', $on) . ")"; + } + $innerJoinClauses = [ + "t1.{$rule['rule_field']} IS NOT NULL", + "t2.{$rule['rule_field']} IS NOT NULL", + "t1.{$rule['rule_field']} = t2.{$rule['rule_field']}", + ]; + + if (in_array(CRM_Dedupe_BAO_DedupeRule::getFieldType($rule['rule_field'], $rule['rule_table']), CRM_Utils_Type::getTextTypes(), TRUE)) { + $innerJoinClauses[] = "t1.{$rule['rule_field']} <> ''"; + $innerJoinClauses[] = "t2.{$rule['rule_field']} <> ''"; + } + return "(" . implode(' AND ', $innerJoinClauses) . ")"; + } + + /** + * @return void + * @throws \Civi\Core\Exception\DBQueryException + */ + public function fillTable(): void { // get the list of queries handy $tableQueries = $this->tableQuery(); @@ -598,4 +747,26 @@ public static function getContactTypeForRuleGroup($rule_group_id) { return \Civi::$statics[__CLASS__]['rule_groups'][$rule_group_id]['contact_type']; } + /** + * Is a file based reserved query configured. + * + * File based reserved queries were an early idea about how to optimise the dedupe queries. + * + * In theory extensions could implement them although there is no evidence any of them have. + * However, if these are implemented by core or by extensions we should not attempt to optimise + * the query by (e.g.) combining queries. + * + * In practice the queries implemented only return one query anyway + * + * @see \CRM_Dedupe_BAO_QueryBuilder_IndividualGeneral + * @see \CRM_Dedupe_BAO_QueryBuilder_IndividualSupervised + * @see \CRM_Dedupe_BAO_QueryBuilder_IndividualUnsupervised + * + * @return bool + */ + private function isUseReservedQuery(): bool { + return $this->is_reserved && + CRM_Utils_File::isIncludable("CRM/Dedupe/BAO/QueryBuilder/{$this->name}.php"); + } + } diff --git a/www/modules/civicrm/CRM/Dedupe/DAO/DedupeException.php b/www/modules/civicrm/CRM/Dedupe/DAO/DedupeException.php index dbf9debde..8a66eb9fe 100644 --- a/www/modules/civicrm/CRM/Dedupe/DAO/DedupeException.php +++ b/www/modules/civicrm/CRM/Dedupe/DAO/DedupeException.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Dedupe/DedupeException.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:4165d0a98662caa2de6fcefbde15c2e2) + * (GenCodeChecksum:4859256449b52a810c2a8d8b5bfcbae6) */ /** diff --git a/www/modules/civicrm/CRM/Dedupe/DAO/DedupeRule.php b/www/modules/civicrm/CRM/Dedupe/DAO/DedupeRule.php index 058a89571..36b9459f4 100644 --- a/www/modules/civicrm/CRM/Dedupe/DAO/DedupeRule.php +++ b/www/modules/civicrm/CRM/Dedupe/DAO/DedupeRule.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Dedupe/DedupeRule.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:bf2a66e6cf05d4aba90d5de0732674f0) + * (GenCodeChecksum:303154da214602fe1fc2d69525e6c57b) */ /** diff --git a/www/modules/civicrm/CRM/Dedupe/DAO/DedupeRuleGroup.php b/www/modules/civicrm/CRM/Dedupe/DAO/DedupeRuleGroup.php index e6ff7ce20..01786410f 100644 --- a/www/modules/civicrm/CRM/Dedupe/DAO/DedupeRuleGroup.php +++ b/www/modules/civicrm/CRM/Dedupe/DAO/DedupeRuleGroup.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Dedupe/DedupeRuleGroup.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:3df551f805f32bd24ae567e40237cac4) + * (GenCodeChecksum:2771f81720658fcb04fdeafde9a04451) */ /** diff --git a/www/modules/civicrm/CRM/Dedupe/Finder.php b/www/modules/civicrm/CRM/Dedupe/Finder.php index e6082c455..c801c6bed 100644 --- a/www/modules/civicrm/CRM/Dedupe/Finder.php +++ b/www/modules/civicrm/CRM/Dedupe/Finder.php @@ -160,6 +160,23 @@ public static function dupesInGroup($rgid, $gid, $searchLimit = 0) { return []; } + /** + * @param array $fields + * @param array $flat + * @param string $ctype + * + * @throws \CRM_Core_Exception + */ + private static function appendCustomDataFields(array &$fields, array &$flat, string $ctype): void { + $subTypes = $fields['contact_sub_type'] ?? []; + // Only return custom for subType + unrestricted or return all custom + // fields. + $customFields = self::getTree($ctype, $subTypes, $fields); + foreach ($customFields as $customField) { + $flat[$customField['column_name']] = $customField['customValue']['data'] ?? NULL; + } + } + /** * A hackish function needed to massage CRM_Contact_Form_$ctype::formRule() * object into a valid $params array for dedupe @@ -185,6 +202,7 @@ public static function formatParams($fields, $ctype) { ]; foreach (['individual_suffix', 'individual_prefix', 'gender'] as $name) { if (!empty($fields[$name])) { + CRM_Core_Error::deprecatedWarning('code thought to be unreachable - slated for removal'); $flat[$replace_these[$name]] = $flat[$name]; unset($flat[$name]); } @@ -217,20 +235,7 @@ public static function formatParams($fields, $ctype) { } // handle custom data - - $subTypes = $fields['contact_sub_type'] ?? []; - // Only return custom for subType + unrestricted or return all custom - // fields. - $tree = CRM_Core_BAO_CustomGroup::getTree($ctype, NULL, NULL, -1, $subTypes, NULL, TRUE, NULL, TRUE); - CRM_Core_BAO_CustomGroup::postProcess($tree, $fields, TRUE); - foreach ($tree as $key => $cg) { - if (!is_int($key)) { - continue; - } - foreach ($cg['fields'] as $cf) { - $flat[$cf['column_name']] = $cf['customValue']['data'] ?? NULL; - } - } + self::appendCustomDataFields($fields, $flat, $ctype); // if the key is dotted, keep just the last part of it foreach ($flat as $key => $value) { @@ -297,63 +302,178 @@ public static function formatParams($fields, $ctype) { } /** - * Parse duplicate pairs into a standardised array and store in the prev_next_cache. + * @param string $entityType + * Of the contact whose contact type is needed. + * @param array $subTypes + * @param array $params * - * @param array $foundDupes - * @param string $cacheKeyString + * @return array[] + * The returned array is keyed by group id and has the custom group table fields + * and a subkey 'fields' holding the specific custom fields. + * If entityId is passed in the fields keys have a subkey 'customValue' which holds custom data + * if set for the given entity. This is structured as an array of values with each one having the keys 'id', 'data' + * + * @throws \CRM_Core_Exception + * @deprecated Function demonstrates just how bad code can get from 20 years of entropy. + * + * This function takes an overcomplicated set of params and returns an overcomplicated + * mix of custom groups, custom fields, custom values (if passed $entityID), and other random stuff. + * + * @see CRM_Core_BAO_CustomGroup::getAll() + * for a better alternative to fetching a tree of custom groups and fields. + * + * @see APIv4::get() + * for a better alternative to fetching entity values. * - * @return array - * Dupe pairs with the keys - * -srcID - * -srcName - * -dstID - * -dstName - * -weight - * -canMerge */ - public static function parseAndStoreDupePairs($foundDupes, $cacheKeyString) { - $cids = []; - foreach ($foundDupes as $dupe) { - $cids[$dupe[0]] = 1; - $cids[$dupe[1]] = 1; + private static function getTree($entityType, $subTypes, $params) { + if (!is_array($subTypes)) { + if (empty($subTypes)) { + $subTypes = []; + } + else { + if (strpos($subTypes, ',') !== FALSE) { + CRM_Core_Error::deprecatedWarning('subtype should be an array, if multiple'); + $subTypes = explode(',', $subTypes); + } + elseif (strpos($subTypes, CRM_Core_DAO::VALUE_SEPARATOR) !== FALSE) { + CRM_Core_Error::deprecatedWarning('subtype should be an array, if multiple'); + $subTypes = explode(CRM_Core_DAO::VALUE_SEPARATOR, trim($subTypes, CRM_Core_DAO::VALUE_SEPARATOR)); + } + else { + $subTypes = (array) $subTypes; + } + } + } + foreach ($subTypes as $index => $subType) { + if (trim($subType, CRM_Core_DAO::VALUE_SEPARATOR) !== $subType) { + CRM_Core_Error::deprecatedWarning('subtype should not require extra cleanup'); + $subTypes[$index] = trim($subType, CRM_Core_DAO::VALUE_SEPARATOR); + } + $validatedSubType = self::validateSubTypeByEntity($entityType, $subType); + if ($subType !== $validatedSubType) { + if (strtolower($subType) === strtolower($validatedSubType)) { + CRM_Core_Error::deprecatedWarning('passing in contact subtype with incorrect capitalization is deprecated'); + $subTypes[$index] = $validatedSubType; + } + else { + // This is a security check rather than a deprecation. + \Civi::log()->warning('invalid subtype passed to duplicate check {type}', ['type' => $subType]); + unset($subTypes[$index]); + } + } } - $cidString = implode(', ', array_keys($cids)); - $dao = CRM_Core_DAO::executeQuery("SELECT id, display_name FROM civicrm_contact WHERE id IN ($cidString) ORDER BY sort_name"); - $displayNames = []; - while ($dao->fetch()) { - $displayNames[$dao->id] = $dao->display_name; + $filters = [ + 'extends' => $entityType, + 'is_active' => TRUE, + ]; + if ($subTypes) { + foreach ($subTypes as $subType) { + $filters['extends_entity_column_value'][] = $subType; + } + $filters['extends_entity_column_value'][] = NULL; } - $userId = CRM_Core_Session::getLoggedInContactID(); - foreach ($foundDupes as $dupes) { - $srcID = $dupes[1]; - $dstID = $dupes[0]; - // The logged in user should never be the src (ie. the contact to be removed). - if ($srcID == $userId) { - $srcID = $dstID; - $dstID = $userId; + $customGroups = CRM_Core_BAO_CustomGroup::getAll($filters, CRM_Core_Permission::EDIT); + $customFields = []; + foreach ($customGroups as $group) { + foreach ($group['fields'] as $field) { + $customFields[$field['id']] = $field; + } + } + + foreach ($customFields as $field) { + $fieldId = $field['id']; + $serialize = CRM_Core_BAO_CustomField::isSerialized($field); + + // Reset all checkbox, radio and multiselect data + if ($field['html_type'] === 'Radio' || $serialize) { + $customFields[$fieldId]['customValue']['data'] = 'NULL'; + } + + $v = NULL; + foreach ($params as $key => $val) { + if (preg_match('/^custom_(\d+)_?(-?\d+)?$/', $key, $match) && + $match[1] == $field['id'] + ) { + $v = $val; + } + } + + if (!isset($customFields[$fieldId]['customValue'])) { + // field exists in db so populate value from "form". + $customFields[$fieldId]['customValue'] = []; + } + + // Serialize checkbox and multi-select data (using array keys for checkbox) + if ($serialize) { + $v = ($v && $field['html_type'] === 'Checkbox') ? array_keys($v) : $v; + $v = $v ? CRM_Utils_Array::implodePadded($v) : NULL; } - $mainContacts[] = $row = [ - 'dstID' => (int) $dstID, - 'dstName' => $displayNames[$dstID], - 'srcID' => (int) $srcID, - 'srcName' => $displayNames[$srcID], - 'weight' => $dupes[2], - 'canMerge' => TRUE, - ]; - - CRM_Core_DAO::executeQuery("INSERT INTO civicrm_prevnext_cache (entity_table, entity_id1, entity_id2, cacheKey, data) VALUES - ('civicrm_contact', %1, %2, %3, %4)", [ - 1 => [$dstID, 'Integer'], - 2 => [$srcID, 'Integer'], - 3 => [$cacheKeyString, 'String'], - 4 => [serialize($row), 'String'], - ] - ); + switch ($field['html_type']) { + + case 'Select Date': + $date = CRM_Utils_Date::processDate($v); + $customFields[$fieldId]['customValue']['data'] = $date; + break; + + case 'File': + break; + + default: + $customFields[$fieldId]['customValue']['data'] = $v; + break; + } + } + + return $customFields; + } + + /** + * Validates contact subtypes and event types. + * + * Performs case-insensitive matching of strings and outputs the correct case. + * e.g. an input of "meeting" would output "Meeting". + * + * For all other entities, it doesn't validate except to check the subtype is an integer. + * + * @param string $entityType + * @param string $subType + * + * @return string + * @throws \CRM_Core_Exception + */ + private static function validateSubTypeByEntity($entityType, $subType) { + + if (is_numeric($subType)) { + return $subType; + } + + $contactTypes = CRM_Contact_BAO_ContactType::basicTypeInfo(TRUE); + $contactTypes['Contact'] = 1; + + if (!array_key_exists($entityType, $contactTypes)) { + throw new CRM_Core_Exception('Invalid Entity Filter'); + } + + $subTypes = CRM_Contact_BAO_ContactType::subTypeInfo($entityType, TRUE); + $subTypes = array_column($subTypes, 'name', 'name'); + // When you create a new contact type it gets saved in mixed case in the database. + // Eg. "Service User" becomes "Service_User" in civicrm_contact_type.name + // But that field does not differentiate case (eg. you can't add Service_User and service_user because mysql will report a duplicate error) + // webform_civicrm and some other integrations pass in the name as lowercase to API3 Contact.duplicatecheck + // Since we can't actually have two strings with different cases in the database perform a case-insensitive search here: + $subTypesByName = array_combine($subTypes, $subTypes); + $subTypesByName = array_change_key_case($subTypesByName, CASE_LOWER); + $subTypesByKey = array_change_key_case($subTypes, CASE_LOWER); + $subTypeKey = mb_strtolower($subType); + if (!array_key_exists($subTypeKey, $subTypesByKey) && !in_array($subTypeKey, $subTypesByName)) { + \Civi::log()->debug("entityType: {$entityType}; subType: {$subType}"); + throw new CRM_Core_Exception('Invalid Filter'); } - return $mainContacts; + return $subTypesByName[$subTypeKey] ?? $subTypesByKey[$subTypeKey]; } } diff --git a/www/modules/civicrm/CRM/Dedupe/MergeHandler.php b/www/modules/civicrm/CRM/Dedupe/MergeHandler.php index cf64201bc..62c6071a4 100644 --- a/www/modules/civicrm/CRM/Dedupe/MergeHandler.php +++ b/www/modules/civicrm/CRM/Dedupe/MergeHandler.php @@ -448,7 +448,7 @@ public function mergeLocations(): void { foreach ($blocksDAO as $blockDAOs) { if (!empty($blockDAOs['update'])) { foreach ($blockDAOs['update'] as $blockDAO) { - $entity = CRM_Core_DAO_AllCoreTables::getBriefName(get_class($blockDAO)); + $entity = CRM_Core_DAO_AllCoreTables::getEntityNameForClass(get_class($blockDAO)); $values = ['checkPermissions' => FALSE]; foreach ($blockDAO->fields() as $field) { if (isset($blockDAO->{$field['name']})) { @@ -460,7 +460,7 @@ public function mergeLocations(): void { } if (!empty($blockDAOs['delete'])) { foreach ($blockDAOs['delete'] as $blockDAO) { - $entity = CRM_Core_DAO_AllCoreTables::getBriefName(get_class($blockDAO)); + $entity = CRM_Core_DAO_AllCoreTables::getEntityNameForClass(get_class($blockDAO)); civicrm_api4($entity, 'delete', ['where' => [['id', '=', $blockDAO->id]], 'checkPermissions' => FALSE]); } } diff --git a/www/modules/civicrm/CRM/Dedupe/Merger.php b/www/modules/civicrm/CRM/Dedupe/Merger.php index 6406e058e..f2f8cbea5 100644 --- a/www/modules/civicrm/CRM/Dedupe/Merger.php +++ b/www/modules/civicrm/CRM/Dedupe/Merger.php @@ -10,7 +10,6 @@ */ use Civi\Api4\Contact; -use Civi\Api4\CustomGroup; /** * @@ -624,15 +623,14 @@ public static function moveContactBelongings($mergeHandler, $tables, $tableOpera * @param array $cidRefs * * @throws \CRM_Core_Exception - * @throws \Civi\API\Exception\UnauthorizedException */ protected static function filterRowBasedCustomDataFromCustomTables(array &$cidRefs) { - $customTables = (array) CustomGroup::get(FALSE) - ->setSelect(['table_name']) - ->addWhere('is_multiple', '=', 0) - ->addWhere('extends', 'IN', array_merge(['Contact'], CRM_Contact_BAO_ContactType::contactTypes())) - ->execute() - ->indexBy('table_name'); + $filters = [ + 'is_multiple' => FALSE, + 'extends' => 'Contact', + ]; + $customGroups = CRM_Core_BAO_CustomGroup::getAll($filters); + $customTables = array_column($customGroups, NULL, 'table_name'); foreach (array_intersect_key($cidRefs, $customTables) as $tableName => $cidSpec) { if (in_array('entity_id', $cidSpec, TRUE)) { unset($cidRefs[$tableName][array_search('entity_id', $cidSpec, TRUE)]); @@ -731,25 +729,6 @@ private static function formatProfileContactParams( ) { $data['contact_sub_type'] = CRM_Utils_Array::implodePadded($params['contact_sub_type']); } - elseif (array_key_exists('contact_sub_type_hidden', $params) && - !empty($params['contact_sub_type_hidden']) - ) { - CRM_Core_Error::deprecatedWarning('code should be unreachable, slated for removal'); - // if profile was used, and had any subtype, we obtain it from there - //CRM-13596 - add to existing contact types, rather than overwriting - if (empty($data['contact_sub_type'])) { - // If we don't have a contact ID the $data['contact_sub_type'] will not be defined... - $data['contact_sub_type'] = CRM_Utils_Array::implodePadded($params['contact_sub_type_hidden']); - } - else { - $data_contact_sub_type_arr = CRM_Utils_Array::explodePadded($data['contact_sub_type']); - if (!in_array($params['contact_sub_type_hidden'], $data_contact_sub_type_arr)) { - //CRM-20517 - make sure contact_sub_type gets the correct delimiters - $data['contact_sub_type'] = trim($data['contact_sub_type'], CRM_Core_DAO::VALUE_SEPARATOR); - $data['contact_sub_type'] = CRM_Core_DAO::VALUE_SEPARATOR . $data['contact_sub_type'] . CRM_Utils_Array::implodePadded($params['contact_sub_type_hidden']); - } - } - } $locationType = []; $count = 1; @@ -953,15 +932,9 @@ private static function formatProfileContactParams( } //CRM-13596 - check for contact_sub_type_hidden first - if (array_key_exists('contact_sub_type_hidden', $params)) { - CRM_Core_Error::deprecatedWarning('code should be unreachable, slated for removal'); - $type = $params['contact_sub_type_hidden']; - } - else { - $type = $data['contact_type']; - if (!empty($data['contact_sub_type'])) { - $type = CRM_Utils_Array::explodePadded($data['contact_sub_type']); - } + $type = $data['contact_type']; + if (!empty($data['contact_sub_type'])) { + $type = CRM_Utils_Array::explodePadded($data['contact_sub_type']); } CRM_Core_BAO_CustomField::formatCustomField($customFieldId, @@ -1633,41 +1606,48 @@ public static function getRowsElementsAndInfo(int $mainID, int $otherID, bool $c } // handle custom fields - $mainTree = self::getTree($main['contact_type'], $mainID, - $main['contact_sub_type'] ?? NULL, - $checkPermissions ? CRM_Core_Permission::EDIT : FALSE - ); - $otherTree = self::getTree($main['contact_type'], $otherID, - $other['contact_sub_type'] ?? NULL, - $checkPermissions ? CRM_Core_Permission::EDIT : FALSE - ); - - foreach ($otherTree as $gid => $group) { - if (!isset($group['fields'])) { - continue; - } - - foreach ($group['fields'] as $fid => $field) { - $mainContactValue = $mainTree[$gid]['fields'][$fid]['customValue'] ?? NULL; - $otherContactValue = $otherTree[$gid]['fields'][$fid]['customValue'] ?? NULL; + $filters = [ + 'extends' => $main['contact_type'], + 'is_active' => TRUE, + 'is_multiple' => FALSE, + 'extends_entity_column_value' => NULL, + ]; + if (!empty($other['contact_sub_type'])) { + $filters['extends_entity_column_value'] = array_merge([NULL], $other['contact_sub_type']); + } + $otherTree = CRM_Core_BAO_CustomGroup::getAll($filters, $checkPermissions ? CRM_Core_Permission::EDIT : NULL); + + foreach ($otherTree as $group) { + $mainCustomValues = Contact::get(FALSE) + ->addWhere('id', '=', $mainID) + ->addSelect($group['name'] . '.*') + ->execute(); + $otherCustomValues = Contact::get(FALSE) + ->addWhere('id', '=', $otherID) + ->addSelect($group['name'] . '.*') + ->execute(); + foreach ($group['fields'] as $field) { + $fid = $field['id']; + $apiFieldName = "{$group['name']}.{$field['name']}"; if (in_array($fid, $compareFields['custom'])) { - $rows["custom_group_$gid"]['title'] ??= $group['title']; + $rows["custom_group_{$group['id']}"]['title'] ??= $group['title']; - if ($mainContactValue) { - foreach ($mainContactValue as $valueId => $values) { - $rows["move_custom_$fid"]['main'] = CRM_Core_BAO_CustomField::displayValue($values['data'], $fid); - } + foreach ($mainCustomValues as $values) { + $customValue = $values[$apiFieldName] ?? NULL; + $rows["move_custom_$fid"]['main'] = CRM_Core_BAO_CustomField::displayValue($customValue, $fid); } $value = 'null'; - if ($otherContactValue) { - foreach ($otherContactValue as $valueId => $values) { - $rows["move_custom_$fid"]['other'] = CRM_Core_BAO_CustomField::displayValue($values['data'], $fid); - if ($values['data'] === 0 || $values['data'] === '0') { - $values['data'] = $qfZeroBug; - } - $value = ($values['data']) ? $values['data'] : $value; + foreach ($otherCustomValues as $values) { + $customValue = $values[$apiFieldName] ?? NULL; + $rows["move_custom_$fid"]['other'] = CRM_Core_BAO_CustomField::displayValue($customValue, $fid); + if ($customValue === 0 || $customValue === '0' || $customValue === FALSE) { + $customValue = $qfZeroBug; } + // FIXME: Not changed during refactor but this looks wrong: should be `??` not `?:` + $value = $customValue ?: $value; } + // FIXME: Underlying code relies on $value to be a string. + $value = is_array($value) ? CRM_Utils_Array::implodePadded($value) : (string) $value; $rows["move_custom_$fid"]['title'] = $field['label']; $elements[] = [ @@ -1699,258 +1679,6 @@ public static function getRowsElementsAndInfo(int $mainID, int $otherID, bool $c return $result; } - /** - * Function is separated from shared function & can likely be distilled to an api call. - * - * @todo clean up post split. - * - * Get custom groups/fields data for type of entity in a tree structure representing group->field hierarchy - * This may also include entity specific data values. - * - * An array containing all custom groups and their custom fields is returned. - * - * @param string $entityType - * Of the contact whose contact type is needed. - * @param int $entityID - * @param array $subTypes - * @param bool|int $checkPermission - * Either a CRM_Core_Permission constant or FALSE to disable checks - * - * @return array - * Custom field 'tree'. - * - * The returned array is keyed by group id and has the custom group table fields - * and a sub-key 'fields' holding the specific custom fields. - * If entityId is passed in the fields keys have a subkey 'customValue' which holds custom data - * if set for the given entity. This is structured as an array of values with each one having the keys 'id', 'data' - * - * @todo - review this - It also returns an array called 'info' with tables, select, from, where keys - * The reason for the info array in unclear and it could be determined from parsing the group tree after creation - * With caching the performance impact would be small & the function would be cleaner - * - * @throws \CRM_Core_Exception - */ - private static function getTree( - $entityType, - int $entityID, - $subTypes, - $checkPermission - ) { - - if (!is_array($subTypes)) { - if (empty($subTypes)) { - $subTypes = []; - } - else { - if (stristr($subTypes, ',')) { - $subTypes = explode(',', $subTypes); - } - else { - $subTypes = explode(CRM_Core_DAO::VALUE_SEPARATOR, trim($subTypes, CRM_Core_DAO::VALUE_SEPARATOR)); - } - } - } - - // create a new tree - - // legacy hardcoded list of data to return - $toReturn = [ - 'custom_field' => [ - 'id', - 'name', - 'label', - 'column_name', - 'data_type', - 'html_type', - 'default_value', - 'attributes', - 'is_required', - 'is_view', - 'help_pre', - 'help_post', - 'options_per_line', - 'start_date_years', - 'end_date_years', - 'date_format', - 'time_format', - 'option_group_id', - 'in_selector', - 'serialize', - ], - 'custom_group' => [ - 'id', - 'name', - 'table_name', - 'title', - 'help_pre', - 'help_post', - 'collapse_display', - 'style', - 'is_multiple', - 'extends', - 'extends_entity_column_id', - 'extends_entity_column_value', - 'max_multiple', - 'is_public', - ], - ]; - - // create select - $select = []; - foreach ($toReturn as $tableName => $tableColumn) { - foreach ($tableColumn as $columnName) { - $select[] = "civicrm_{$tableName}.{$columnName} as civicrm_{$tableName}_{$columnName}"; - } - } - $strSelect = 'SELECT ' . implode(', ', $select); - - // from, where, order by - $strFrom = ' -FROM civicrm_custom_group -LEFT JOIN civicrm_custom_field ON (civicrm_custom_field.custom_group_id = civicrm_custom_group.id) -'; - - // if entity is either individual, organization or household pls get custom groups for 'contact' too. - if ($entityType === 'Individual' || $entityType === 'Organization' || - $entityType === 'Household' - ) { - $in = "'$entityType', 'Contact'"; - } - elseif (strpos($entityType, "'") !== FALSE) { - // this allows the calling function to send in multiple entity types - $in = $entityType; - } - else { - // quote it - $in = "'$entityType'"; - } - - $params = []; - $sqlParamKey = 1; - $subType = ''; - if (!empty($subTypes)) { - foreach ($subTypes as $key => $subType) { - $subTypeClauses[] = self::whereListHas("civicrm_custom_group.extends_entity_column_value", CRM_Core_BAO_CustomGroup::validateSubTypeByEntity($entityType, $subType)); - } - $subTypeClause = '(' . implode(' OR ', $subTypeClauses) . ')'; - $subTypeClause = '(' . $subTypeClause . ' OR civicrm_custom_group.extends_entity_column_value IS NULL )'; - - $strWhere = " -WHERE civicrm_custom_group.is_active = 1 - AND civicrm_custom_field.is_active = 1 - AND civicrm_custom_group.extends IN ($in) - AND $subTypeClause -"; - } - else { - $strWhere = " -WHERE civicrm_custom_group.is_active = 1 - AND civicrm_custom_field.is_active = 1 - AND civicrm_custom_group.extends IN ($in) -"; - } - - if ($checkPermission) { - // ensure that the user has access to these custom groups - $strWhere .= " AND " . - CRM_Core_Permission::customGroupClause($checkPermission, - 'civicrm_custom_group.' - ); - } - - $orderBy = " -ORDER BY civicrm_custom_group.weight, - civicrm_custom_group.title, - civicrm_custom_field.weight, - civicrm_custom_field.label -"; - - // final query string - $queryString = "$strSelect $strFrom $strWhere $orderBy"; - - // lets see if we can retrieve the groupTree from cache - $cacheString = $queryString . '_Inline'; - - $cacheKey = 'CRM_Core_DAO_CustomGroup_Query ' . md5($cacheString); - $multipleFieldGroupCacheKey = 'CRM_Core_DAO_CustomGroup_QueryMultipleFields ' . md5($cacheString); - $cache = CRM_Utils_Cache::singleton(); - $groupTree = $cache->get($cacheKey); - $multipleFieldGroups = $cache->get($multipleFieldGroupCacheKey); - - if (empty($groupTree)) { - [$multipleFieldGroups, $groupTree] = CRM_Core_BAO_CustomGroup::buildGroupTree($entityType, $toReturn, $subTypes, $queryString, $params, $subType); - - $cache->set($cacheKey, $groupTree); - $cache->set($multipleFieldGroupCacheKey, $multipleFieldGroups); - } - // entitySelectClauses is an array of select clauses for custom value tables which are not multiple - // and have data for the given entities. $entityMultipleSelectClauses is the same for ones with multiple - $entitySingleSelectClauses = $entityMultipleSelectClauses = $groupTree['info']['select'] = []; - $singleFieldTables = []; - // now that we have all the groups and fields, lets get the values - // since we need to know the table and field names - // add info to groupTree - - if (isset($groupTree['info']) && !empty($groupTree['info']) && - !empty($groupTree['info']['tables']) - ) { - $groupTree['info']['where'] = NULL; - - foreach ($groupTree['info']['tables'] as $table => $fields) { - $groupTree['info']['from'][] = $table; - $select = [ - "{$table}.id as {$table}_id", - "{$table}.entity_id as {$table}_entity_id", - ]; - foreach ($fields as $column => $dontCare) { - $select[] = "{$table}.{$column} as {$table}_{$column}"; - } - $groupTree['info']['select'] = array_merge($groupTree['info']['select'], $select); - if ($entityID) { - $groupTree['info']['where'][] = "{$table}.entity_id = $entityID"; - if (in_array($table, $multipleFieldGroups) && - CRM_Core_BAO_CustomGroup::customGroupDataExistsForEntity($entityID, $table) - ) { - $entityMultipleSelectClauses[$table] = $select; - } - else { - $singleFieldTables[] = $table; - $entitySingleSelectClauses = array_merge($entitySingleSelectClauses, $select); - } - - } - } - if ($entityID && !empty($singleFieldTables)) { - CRM_Core_BAO_CustomGroup::buildEntityTreeSingleFields($groupTree, $entityID, $entitySingleSelectClauses, $singleFieldTables); - } - $multipleFieldTablesWithEntityData = array_keys($entityMultipleSelectClauses); - if (!empty($multipleFieldTablesWithEntityData)) { - CRM_Core_BAO_CustomGroup::buildEntityTreeMultipleFields($groupTree, $entityID, $entityMultipleSelectClauses, $multipleFieldTablesWithEntityData); - } - - } - return $groupTree; - } - - /** - * Suppose you have a SQL column, $column, which includes a delimited list, and you want - * a WHERE condition for rows that include $value. Use whereListHas(). - * - * @param string $column - * @param string $value - * @param string $delimiter - * - * @return string - * SQL condition. - * @throws \CRM_Core_Exception - */ - private static function whereListHas($column, $value, $delimiter = CRM_Core_DAO::VALUE_SEPARATOR) { - // ? - $bareValue = trim($value, $delimiter); - $escapedValue = CRM_Utils_Type::escape("%{$delimiter}{$bareValue}{$delimiter}%", 'String', FALSE); - return "($column LIKE \"$escapedValue\")"; - } - /** * Based on the provided two contact_ids and a set of tables, move the belongings of the * other contact to the main one - be it Location / CustomFields or Contact .. related info. @@ -2036,11 +1764,11 @@ public static function moveAllBelongings($mainId, $otherId, $migrationInfo, $che foreach ($submitted as $key => $value) { if (strpos($key, 'custom_') === 0) { $fieldID = (int) substr($key, 7); - $fieldMetadata = CRM_Core_BAO_CustomField::getCustomFieldsForContactType($contactType, FALSE)[$fieldID] ?? NULL; + $fieldMetadata = CRM_Core_BAO_CustomField::getField($fieldID); if ($fieldMetadata) { $htmlType = (string) $fieldMetadata['html_type']; - $isSerialized = CRM_Core_BAO_CustomField::isSerialized($fieldMetadata); - $isView = (bool) $fieldMetadata['is_view']; + $isSerialized = $fieldMetadata['serialize']; + $isView = $fieldMetadata['is_view']; $submitted = self::processCustomFields($mainId, $key, $submitted, $value, $fieldID, $isView, $htmlType, $isSerialized); if ($isView) { $viewOnlyCustomFields[$key] = $submitted[$key]; @@ -2233,8 +1961,8 @@ public static function getDuplicatePairs($rule_group_id, $group_id, $reloadCache /** * Get the cache key string for the merge action. * - * @param int $rule_group_id - * @param int $group_id + * @param int|null $rule_group_id + * @param int|null $group_id * @param array $criteria * Additional criteria to narrow down the merge group. * Currently we are only supporting the key 'contact' within it. @@ -2250,8 +1978,8 @@ public static function getDuplicatePairs($rule_group_id, $group_id, $reloadCache public static function getMergeCacheKeyString($rule_group_id, $group_id, $criteria, $checkPermissions, $searchLimit) { $contactType = CRM_Dedupe_BAO_DedupeRuleGroup::getContactTypeForRuleGroup($rule_group_id); $cacheKeyString = "merge_{$contactType}"; - $cacheKeyString .= $rule_group_id ? "_{$rule_group_id}" : '_0'; - $cacheKeyString .= $group_id ? "_{$group_id}" : '_0'; + $cacheKeyString .= '_' . (int) $rule_group_id; + $cacheKeyString .= '_' . (int) $group_id; $cacheKeyString .= '_' . (int) $searchLimit; $cacheKeyString .= !empty($criteria) ? md5(serialize($criteria)) : '_0'; if ($checkPermissions) { @@ -2310,11 +2038,6 @@ public static function getMergeFieldsMetadata(bool $checkPermissions = TRUE): ar * @throws CRM_Core_Exception */ public static function getMergeContactDetails($contactID): array { - $params = [ - 'contact_id' => $contactID, - 'version' => 3, - 'return' => array_merge(['display_name'], self::getContactFields()), - ]; $result = Contact::get(FALSE)->addWhere('id', '=', $contactID)->execute()->first(); // CRM-18480: Cancel the process if the contact is already deleted diff --git a/www/modules/civicrm/CRM/Event/ActionMapping.php b/www/modules/civicrm/CRM/Event/ActionMapping.php index 945079443..8bcc38c29 100644 --- a/www/modules/civicrm/CRM/Event/ActionMapping.php +++ b/www/modules/civicrm/CRM/Event/ActionMapping.php @@ -28,7 +28,7 @@ public function getEntityName(): string { return 'Participant'; } - public function modifySpec(\Civi\Api4\Service\Spec\RequestSpec $spec) { + public function modifyApiSpec(\Civi\Api4\Service\Spec\RequestSpec $spec) { $spec->getFieldByName('entity_value') ->setLabel($this->getLabel()); $spec->getFieldByName('entity_status') diff --git a/www/modules/civicrm/CRM/Event/BAO/Event.php b/www/modules/civicrm/CRM/Event/BAO/Event.php index 56c10ee6d..714cf1644 100644 --- a/www/modules/civicrm/CRM/Event/BAO/Event.php +++ b/www/modules/civicrm/CRM/Event/BAO/Event.php @@ -9,6 +9,8 @@ +--------------------------------------------------------------------+ */ +use Civi\Api4\Event\AuthorizeRecordEvent; + /** * * @package CRM @@ -121,10 +123,6 @@ public static function create(&$params) { $event = self::add($params); CRM_Price_BAO_PriceSet::setPriceSets($params, $event, 'event'); - if (is_a($event, 'CRM_Core_Error')) { - CRM_Core_DAO::transaction('ROLLBACK'); - return $event; - } $contactId = CRM_Core_Session::getLoggedInContactID(); if (!$contactId) { @@ -178,8 +176,10 @@ public static function del($id) { public static function self_hook_civicrm_pre(\Civi\Core\Event\PreEvent $event) { if ($event->action === 'delete' && $event->id) { - $extends = ['event']; + $extends = ['Event']; $groupTree = CRM_Core_BAO_CustomGroup::getGroupDetail(NULL, NULL, $extends); + // @todo is this custom field loop necessary? The cascade delete on the + // db foreign key should do it already. foreach ($groupTree as $values) { $query = "DELETE FROM %1 WHERE entity_id = %2"; CRM_Core_DAO::executeQuery($query, [ @@ -277,11 +277,15 @@ public static function getEvents( } elseif ($all == 0) { // find only events ending in the future + // The STR_TO_DATE is because in mariadb with strict mode off you can + // have dates like '0000-00-00' and we want to include those here, but a + // comparison to '' or '0000-00-00' directly will error in mysql8 with + // strict mode on. So this works for both. $endDate = date('YmdHis'); $query .= " AND ( `end_date` >= {$endDate} OR ( - ( end_date IS NULL OR end_date = '' ) AND start_date >= {$endDate} + ( end_date IS NULL OR end_date = STR_TO_DATE('', '%Y%m%d%H%i%s') ) AND start_date >= {$endDate} ) )"; } @@ -1187,13 +1191,13 @@ public static function sendMail($contactID, $values, $participantId, $isTest = F $sendTemplateParams = [ 'workflow' => 'event_online_receipt', - 'contactId' => $contactID, 'isTest' => $isTest, 'tplParams' => $tplParams, 'PDFFilename' => ts('confirmation') . '.pdf', 'modelProps' => [ 'participantID' => (int) $participantId, 'eventID' => (int) $values['event']['id'], + 'contactID' => (int) $contactID, ], ]; @@ -1597,10 +1601,11 @@ public static function displayProfile(&$params, $gid, &$groupTitle, &$values, &$ $idx = $detailName . '_id'; $values[$index] = $params[$idx]; } - elseif ($fieldName == 'im') { + elseif ($fieldName === 'im') { $providerName = NULL; if ($providerId = $detailName . '-provider_id') { - $providerName = $imProviders[$params[$providerId]] ?? NULL; + $inputProvider = $params[$providerId] ?? ''; + $providerName = $imProviders[$inputProvider] ?? NULL; } if ($providerName) { $values[$index] = $params[$detailName] . " (" . $providerName . ")"; @@ -1609,7 +1614,7 @@ public static function displayProfile(&$params, $gid, &$groupTitle, &$values, &$ $values[$index] = $params[$detailName]; } } - elseif ($fieldName == 'phone') { + elseif ($fieldName === 'phone') { $phoneExtField = str_replace('phone', 'phone_ext', $detailName); if (isset($params[$phoneExtField])) { $values[$index] = $params[$detailName] . " (" . $params[$phoneExtField] . ")"; @@ -1678,8 +1683,6 @@ public static function displayProfile(&$params, $gid, &$groupTitle, &$values, &$ $customVal = $params[$name]; } //take the custom field options - $returnProperties = [$name => 1]; - $query = new CRM_Contact_BAO_Query($params, $returnProperties, $fields); if (!$skip) { $displayValue = CRM_Core_BAO_CustomField::displayValue($customVal, $cfID); } @@ -2189,17 +2192,17 @@ public static function getAllPermissions() { } /** - * @param string $entityName - * @param string $action - * @param array $record - * @param int $userID - * @return bool + * Check event access. * @see \Civi\Api4\Utils\CoreUtil::checkAccessRecord */ - public static function _checkAccess(string $entityName, string $action, array $record, $userID): bool { - switch ($action) { + public static function self_civi_api4_authorizeRecord(AuthorizeRecordEvent $e): void { + $record = $e->getRecord(); + $userID = $e->getUserID(); + + switch ($e->getActionName()) { case 'create': - return CRM_Core_Permission::check('access CiviEvent', $userID); + $e->setAuthorized(CRM_Core_Permission::check('access CiviEvent', $userID)); + return; case 'get': $actionType = CRM_Core_Permission::VIEW; @@ -2214,7 +2217,7 @@ public static function _checkAccess(string $entityName, string $action, array $r break; } - return self::checkPermission($record['id'], $actionType, $userID); + $e->setAuthorized(self::checkPermission($record['id'], $actionType, $userID)); } /** diff --git a/www/modules/civicrm/CRM/Event/BAO/Participant.php b/www/modules/civicrm/CRM/Event/BAO/Participant.php index f9f0c1c95..bbcaad1f1 100644 --- a/www/modules/civicrm/CRM/Event/BAO/Participant.php +++ b/www/modules/civicrm/CRM/Event/BAO/Participant.php @@ -170,11 +170,6 @@ public static function create(&$params) { $participant = self::add($params); - if (is_a($participant, 'CRM_Core_Error')) { - $transaction->rollback(); - return $participant; - } - // Log activity when creating new participant or changing status if (empty($params['id']) || (isset($params['status_id']) && $params['status_id'] != $status) @@ -315,8 +310,7 @@ public static function create(&$params) { * calculation or not. (it is for cron job purpose) * * @param bool $returnWaitingCount - * @param bool $considerTestParticipant - * When TRUE, include participant records where is_test = 1. + * @param bool $considerTestParticipant deprecated, unused * @param bool $onlyPositiveStatuses * When FALSE, count all participant statuses where is_counted = 1. This includes * both "Positive" participants (Registered, Attended, etc.) and waitlisted @@ -325,6 +319,7 @@ public static function create(&$params) { * * @return bool|int|null|string * 1. false => If event having some empty spaces. + * @throws \CRM_Core_Exception */ public static function eventFull( $eventId, @@ -345,28 +340,16 @@ public static function eventFull( // It might be case there are some empty spaces and still event // is full, as waitlist might represent group require spaces > empty. - $participantRoles = CRM_Event_PseudoConstant::participantRole(NULL, 'filter = 1'); - $countedStatuses = CRM_Event_PseudoConstant::participantStatus(NULL, 'is_counted = 1'); + $countedStatuses = \CRM_Event_BAO_Participant::buildOptions('status_id', NULL, ['is_counted' => 1]);; $positiveStatuses = CRM_Event_PseudoConstant::participantStatus(NULL, "class = 'Positive'"); $waitingStatuses = CRM_Event_PseudoConstant::participantStatus(NULL, "class = 'Waiting'"); $onWaitlistStatusId = array_search('On waitlist', $waitingStatuses); - $where = [' event.id = %1 ']; - if (!$considerTestParticipant) { - $where[] = ' participant.is_test = 0 '; + $where = [' event.id = %1 ', ' participant.is_test = 0 ']; + $participantRoleClause = self::getParticipantRoleClause(); + if ($participantRoleClause) { + $where[] = " participant.role_id " . $participantRoleClause; } - - // Only count Participant Roles with the "Counted?" flag. - if (!empty($participantRoles)) { - $escapedRoles = []; - foreach (array_keys($participantRoles) as $participantRole) { - $escapedRoles[] = CRM_Utils_Type::escape($participantRole, 'String'); - } - - $regexp = "([[:cntrl:]]|^)" . implode('([[:cntrl:]]|$)|([[:cntrl:]]|^)', $escapedRoles) . "([[:cntrl:]]|$)"; - $where[] = " participant.role_id REGEXP '{$regexp}'"; - } - $eventParams = [1 => [$eventId, 'Positive']]; //in case any waiting, straight forward event is full. @@ -378,22 +361,18 @@ public static function eventFull( $eventSeatsWhere = implode(' AND ', $where) . " AND ( participant.status_id = $onWaitlistStatusId )"; $query = " - SELECT participant.id id, - event.event_full_text as event_full_text + SELECT participant.id id FROM civicrm_participant participant INNER JOIN civicrm_event event ON ( event.id = participant.event_id ) {$whereClause}"; - $eventFullText = ts('This event is full.'); - $participants = CRM_Core_DAO::executeQuery($query, $eventParams); - while ($participants->fetch()) { + $hasWaitlistedParticipants = CRM_Core_DAO::singleValueQuery($query, $eventParams); + if ($hasWaitlistedParticipants) { //oops here event is full and we don't want waiting count. if ($returnWaitingCount) { return CRM_Event_BAO_Event::eventTotalSeats($eventId, $eventSeatsWhere); } - else { - return ($participants->event_full_text) ? $participants->event_full_text : $eventFullText; - } + return CRM_Core_DAO::singleValueQuery('SELECT event_full_text FROM civicrm_event WHERE id = ' . (int) $eventId) ?: ts('This event is full.'); } } @@ -520,7 +499,7 @@ public static function priceSetOptionsCount( SELECT line.id as lineId, line.entity_id as entity_id, line.qty, - value.id as valueId, + value.id as price_field_value_id, value.count, field.html_type FROM civicrm_line_item line @@ -536,14 +515,13 @@ public static function priceSetOptionsCount( $lineItem = CRM_Core_DAO::executeQuery($sql, [1 => [$eventId, 'Positive']]); while ($lineItem->fetch()) { - $count = $lineItem->count; - if (!$count) { - $count = 1; - } - if ($lineItem->html_type == 'Text') { + $id = (int) $lineItem->price_field_value_id; + $optionsCount[$id] ??= 0; + $count = $lineItem->count ?: 1; + if ($lineItem->html_type === 'Text') { $count *= $lineItem->qty; } - $optionsCount[$lineItem->valueId] = $count + ($optionsCount[$lineItem->valueId] ?? 0); + $optionsCount[$id] += $count; } return $optionsCount; @@ -1017,7 +995,6 @@ public static function getUnDiscountedAmountForEventPriceSetFieldValue($eventID, $params[2] = [$discountedPriceFieldOptionID, 'Integer']; } else { - $feeLevel = current($feeLevel); $query = "SELECT cpfv.amount FROM `civicrm_price_field_value` cpfv LEFT JOIN civicrm_price_field cpf ON cpfv.price_field_id = cpf.id WHERE cpf.price_set_id = %1 AND cpfv.label LIKE %2"; @@ -1408,7 +1385,6 @@ public static function sendTransitionParticipantMail( [ 'workflow' => 'participant_' . strtolower($mailType), 'contactId' => $contactId, - 'tokenContext' => ['participantId' => $participantId], 'tplParams' => [ 'participant' => $participantValues, 'event' => $eventDetails, @@ -1419,6 +1395,11 @@ public static function sendTransitionParticipantMail( 'isConfirm' => $mailType === 'Confirm', 'checksumValue' => $checksumValue, ], + 'modelProps' => [ + 'participantID' => (int) $participantId, + 'eventID' => (int) $eventDetails['id'], + 'contactID' => (int) $contactId, + ], 'from' => $receiptFrom, 'toName' => $participantName, 'toEmail' => $toEmail, @@ -1441,10 +1422,7 @@ public static function sendTransitionParticipantMail( 'is_test' => $participantValues['is_test'], 'status_id' => 2, ]; - - if (is_a(CRM_Activity_BAO_Activity::create($activityParams), 'CRM_Core_Error')) { - throw new CRM_Core_Exception('Failed creating Activity for expiration mail'); - } + CRM_Activity_BAO_Activity::create($activityParams); } } @@ -1693,6 +1671,10 @@ public static function getAdditionalParticipantUrl($participantIds) { public static function createDiscountTrxn($eventID, $contributionParams, $feeLevel, $discountedPriceFieldOptionID = NULL) { $financialTypeID = $contributionParams['contribution']->financial_type_id; $total_amount = $contributionParams['total_amount']; + if (is_array($feeLevel)) { + CRM_Core_Error::deprecatedFunctionWarning('array passed for string value'); + $feeLevel = (string) current($feeLevel); + } $checkDiscount = CRM_Core_BAO_Discount::findSet($eventID, 'civicrm_event'); if (!empty($checkDiscount)) { @@ -1780,13 +1762,19 @@ public static function addActivityForSelection($participantId, $activityType) { public static function buildOptions($fieldName, $context = NULL, $props = []) { $params = ['condition' => []]; - if ($fieldName == 'status_id' && $context != 'validate') { + if ($fieldName === 'status_id' && $context !== 'validate') { // Get rid of cart-related option if disabled // FIXME: Why does this option even exist if cart is disabled? if (!Civi::settings()->get('enable_cart')) { $params['condition'][] = "name <> 'Pending in cart'"; } } + if ($fieldName === 'status_id' && isset($props['is_counted'])) { + $params['condition'][] = 'is_counted = ' . $props['is_counted']; + } + if ($fieldName === 'role_id' && isset($props['filter'])) { + $params['condition'][] = 'filter = ' . $props['filter']; + } return CRM_Core_PseudoConstant::get(__CLASS__, $fieldName, $params, $context); } @@ -1930,4 +1918,32 @@ public static function self_hook_civicrm_pre(\Civi\Core\Event\PreEvent $event) { } } + /** + * Get the clause to exclude uncounted participant roles. + * + * @internal do not call from outside core code. + * + * @return string + * @throws \CRM_Core_Exception + */ + public static function getParticipantRoleClause(): string { + // Only count Participant Roles with the "Counted?" flag. + $participantRoles = self::buildOptions('role_id', NULL, ['filter' => TRUE]); + $allRoles = self::buildOptions('role_id'); + if ($participantRoles === $allRoles) { + // Don't complicate the query if no roles are excluded. + return ''; + } + if (!empty($participantRoles)) { + $escapedRoles = []; + foreach (array_keys($participantRoles) as $participantRole) { + $escapedRoles[] = CRM_Utils_Type::escape($participantRole, 'String'); + } + + $regexp = "([[:cntrl:]]|^)" . implode('([[:cntrl:]]|$)|([[:cntrl:]]|^)', $escapedRoles) . "([[:cntrl:]]|$)"; + $participantRoleClause = "REGEXP '{$regexp}'"; + } + return $participantRoleClause ?? ''; + } + } diff --git a/www/modules/civicrm/CRM/Event/Cart/DAO/Cart.php b/www/modules/civicrm/CRM/Event/Cart/DAO/Cart.php index edbf11df8..07d259a2e 100644 --- a/www/modules/civicrm/CRM/Event/Cart/DAO/Cart.php +++ b/www/modules/civicrm/CRM/Event/Cart/DAO/Cart.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Event/Cart/Cart.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:8e893fbe5da167afb44ab4d4d108239c) + * (GenCodeChecksum:6c337a4c740d4d8aeeb55e5a5f51f5c5) */ /** diff --git a/www/modules/civicrm/CRM/Event/Cart/DAO/EventInCart.php b/www/modules/civicrm/CRM/Event/Cart/DAO/EventInCart.php index f7ffdc041..dc0722ca7 100644 --- a/www/modules/civicrm/CRM/Event/Cart/DAO/EventInCart.php +++ b/www/modules/civicrm/CRM/Event/Cart/DAO/EventInCart.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Event/Cart/EventInCart.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:53e6a1294997529a3b2babce613e214c) + * (GenCodeChecksum:dbcfa0b715a597296517664991988807) */ /** diff --git a/www/modules/civicrm/CRM/Event/DAO/Event.php b/www/modules/civicrm/CRM/Event/DAO/Event.php index 32de78e33..16ede636d 100644 --- a/www/modules/civicrm/CRM/Event/DAO/Event.php +++ b/www/modules/civicrm/CRM/Event/DAO/Event.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Event/Event.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:d63c53d10f2e09895a19061ea9d51093) + * (GenCodeChecksum:95fce61846fa118c62a6638222b147ef) */ /** @@ -542,7 +542,7 @@ class CRM_Event_DAO_Event extends CRM_Core_DAO { /** * Number of hours prior to event start date to allow self-service cancellation or transfer. * - * @var int|string|null + * @var int|string * (SQL type: int) * Note that values will be retrieved from the database as a string. */ @@ -1471,7 +1471,7 @@ public static function &fields() { 'bao' => 'CRM_Event_BAO_Event', 'localizable' => 1, 'html' => [ - 'type' => 'TextArea', + 'type' => 'RichTextEditor', ], 'add' => '1.7', ], @@ -1994,6 +1994,7 @@ public static function &fields() { 'type' => CRM_Utils_Type::T_INT, 'title' => ts('Self-service Cancellation or Transfer Time'), 'description' => ts('Number of hours prior to event start date to allow self-service cancellation or transfer.'), + 'required' => TRUE, 'usage' => [ 'import' => FALSE, 'export' => FALSE, diff --git a/www/modules/civicrm/CRM/Event/DAO/Participant.php b/www/modules/civicrm/CRM/Event/DAO/Participant.php index 5821da041..f8b518b60 100644 --- a/www/modules/civicrm/CRM/Event/DAO/Participant.php +++ b/www/modules/civicrm/CRM/Event/DAO/Participant.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Event/Participant.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:9467fd6b3257cf173c367e78d7875b82) + * (GenCodeChecksum:020be0037929bdfd423d5bfcc6141601) */ /** @@ -47,6 +47,7 @@ class CRM_Event_DAO_Participant extends CRM_Core_DAO { 'add' => 'civicrm/participant/add?action=add&context=standalone&reset=1', 'view' => 'civicrm/contact/view/participant?id=[id]&cid=[contact_id]&action=view&reset=1', 'update' => 'civicrm/contact/view/participant?id=[id]&cid=[contact_id]&action=update&reset=1', + 'detach' => 'civicrm/event/selfsvcupdate?reset=1&pid=[id]&is_backoffice=1', 'delete' => 'civicrm/participant/delete?id=[id]&reset=1', ]; @@ -504,6 +505,9 @@ public static function &fields() { 'entity' => 'Participant', 'bao' => 'CRM_Event_BAO_Participant', 'localizable' => 0, + 'html' => [ + 'type' => 'Radio', + ], 'add' => '1.7', ], 'participant_is_pay_later' => [ @@ -526,6 +530,9 @@ public static function &fields() { 'entity' => 'Participant', 'bao' => 'CRM_Event_BAO_Participant', 'localizable' => 0, + 'html' => [ + 'type' => 'Radio', + ], 'add' => '2.1', ], 'participant_fee_amount' => [ @@ -552,6 +559,9 @@ public static function &fields() { 'entity' => 'Participant', 'bao' => 'CRM_Event_BAO_Participant', 'localizable' => 0, + 'html' => [ + 'type' => 'Text', + ], 'add' => '2.1', ], 'participant_registered_by_id' => [ diff --git a/www/modules/civicrm/CRM/Event/DAO/ParticipantPayment.php b/www/modules/civicrm/CRM/Event/DAO/ParticipantPayment.php index 68e9a12ec..3792184fc 100644 --- a/www/modules/civicrm/CRM/Event/DAO/ParticipantPayment.php +++ b/www/modules/civicrm/CRM/Event/DAO/ParticipantPayment.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Event/ParticipantPayment.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:54606fa6b25d1f6db3229594b6e3eef0) + * (GenCodeChecksum:3809a144294ce19cbe0c2277bfbd6ef4) */ /** diff --git a/www/modules/civicrm/CRM/Event/DAO/ParticipantStatusType.php b/www/modules/civicrm/CRM/Event/DAO/ParticipantStatusType.php index f052e419e..d08afa5ef 100644 --- a/www/modules/civicrm/CRM/Event/DAO/ParticipantStatusType.php +++ b/www/modules/civicrm/CRM/Event/DAO/ParticipantStatusType.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Event/ParticipantStatusType.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:826624f3fa543f7c7a93439ddb53b150) + * (GenCodeChecksum:347f317d7b8a3cb912c7274b62f7f555) */ /** diff --git a/www/modules/civicrm/CRM/Event/Form/EventFees.php b/www/modules/civicrm/CRM/Event/Form/EventFees.php index 8da6bdeaa..6999f151d 100644 --- a/www/modules/civicrm/CRM/Event/Form/EventFees.php +++ b/www/modules/civicrm/CRM/Event/Form/EventFees.php @@ -57,6 +57,7 @@ public static function preProcess(&$form) { */ public static function setDefaultValues(&$form) { $defaults = []; + $billingLocationTypeID = CRM_Core_BAO_LocationType::getBilling(); if ($form->_pId) { $ids = []; @@ -78,20 +79,20 @@ public static function setDefaultValues(&$form) { $form->assign('discount', $discounts[$defaults['discount_id']]); } - $form->assign('fee_amount', CRM_Utils_Array::value('fee_amount', $defaults)); - $form->assign('fee_level', CRM_Utils_Array::value('fee_level', $defaults)); + $form->assign('fee_amount', $defaults['fee_amount'] ?? NULL); + $form->assign('fee_level', $defaults['fee_level'] ?? NULL); } } if ($form->_mode) { $config = CRM_Core_Config::singleton(); // set default country from config if no country set - if (empty($defaults["billing_country_id-{$form->_bltID}"])) { - $defaults["billing_country_id-{$form->_bltID}"] = $config->defaultContactCountry; + if (empty($defaults["billing_country_id-{$billingLocationTypeID}"])) { + $defaults["billing_country_id-{$billingLocationTypeID}"] = $config->defaultContactCountry; } - if (empty($defaults["billing_state_province_id-{$form->_bltID}"])) { - $defaults["billing_state_province_id-{$form->_bltID}"] = $config->defaultContactStateProvince; + if (empty($defaults["billing_state_province_id-{$billingLocationTypeID}"])) { + $defaults["billing_state_province_id-{$billingLocationTypeID}"] = $config->defaultContactStateProvince; } $billingDefaults = $form->getProfileDefaults('Billing', $form->_contactId); @@ -162,18 +163,19 @@ public static function setDefaultValues(&$form) { } } - $form->assign('totalAmount', CRM_Utils_Array::value('fee_amount', $defaults)); + $form->assign('totalAmount', $defaults['fee_amount'] ?? NULL); if ($form->_action == CRM_Core_Action::UPDATE) { $fee_level = $defaults['fee_level']; CRM_Event_BAO_Participant::fixEventLevel($fee_level); $form->assign('fee_level', $fee_level); - $form->assign('fee_amount', CRM_Utils_Array::value('fee_amount', $defaults)); + $form->assign('fee_amount', $defaults['fee_amount'] ?? NULL); } } //CRM-4453 if (!empty($defaults['participant_fee_currency'])) { $form->assign('fee_currency', $defaults['participant_fee_currency']); + $form->assign('currency', $defaults['participant_fee_currency']); } // CRM-4395 diff --git a/www/modules/civicrm/CRM/Event/Form/EventFormTrait.php b/www/modules/civicrm/CRM/Event/Form/EventFormTrait.php index 442cf5619..823537d57 100644 --- a/www/modules/civicrm/CRM/Event/Form/EventFormTrait.php +++ b/www/modules/civicrm/CRM/Event/Form/EventFormTrait.php @@ -30,6 +30,14 @@ trait CRM_Event_Form_EventFormTrait { */ public function getEventValue(string $fieldName) { if ($this->isDefined('Event')) { + if ($fieldName === 'available_spaces') { + // This is temporary. Apiv4 returns available_spaces as a calculated field. + // However, there is not total parity between it and the old function. + // That needs to be worked through - per https://lab.civicrm.org/dev/core/-/issues/4907 + // In order to allow the forms to switch over to the 'final' signature we + // re-direct to the old function for now. + return $this->getAvailableSpaces(); + } return $this->lookup('Event', $fieldName); } $id = $this->getEventID(); @@ -48,10 +56,14 @@ public function getEventValue(string $fieldName) { * is only given where there is specific test cover. * * @noinspection PhpUnhandledExceptionInspection - * @noinspection PhpDocMissingThrowsInspection */ public function getEventID(): ?int { - throw new CRM_Core_Exception('`getEventID` must be implemented'); + try { + return $this->getParticipantValue('event_id'); + } + catch (CRM_Core_Exception $e) { + throw new CRM_Core_Exception('`getEventID` must be implemented'); + } } /** @@ -92,4 +104,65 @@ public function getParticipantValue(string $fieldName) { return NULL; } + /** + * Get the number of available spaces in the given event. + * + * @internal this is a transitional function - eventually it will be removed + * and the api will handle it. + * + * @return int + * + * @throws \CRM_Core_Exception + */ + protected function getAvailableSpaces(): int { + $availableSpaces = CRM_Event_BAO_Participant::eventFull($this->getEventID(), + TRUE, + $this->getEventValue('has_waitlist') + ); + return is_numeric($availableSpaces) ? (int) $availableSpaces : 0; + } + + /** + * Is the event full already. + * + * This function may be calculated by v4 api in time, in which case the function + * will call that instead but will remain available. + * + * @return bool + * + * @throws \CRM_Core_Exception + * @api This function will not change in a minor release and is supported for + * use outside of core. This annotation / external support for properties + * is only given where there is specific test cover. + */ + public function isEventFull(): bool { + $maximum = $this->getEventValue('max_participants'); + return !($maximum === NULL) && $this->getEventValue('available_spaces') < 1; + } + + /** + * Is the event ready for online registrations. + * + * @internal - the handling of waitlist in this function may change. + * + * @return bool + * @throws \CRM_Core_Exception + */ + protected function isAvailableForOnlineRegistration(): bool { + if (!$this->getEventValue('is_online_registration')) { + return FALSE; + } + if ($this->isEventFull() && !$this->getEventValue('has_waitlist')) { + return FALSE; + } + if (!CRM_Event_BAO_Event::validRegistrationRequest([ + 'registration_start_date' => $this->getEventValue('registration_start_date'), + 'registration_end_date' => $this->getEventValue('registration_end_date'), + 'end_date' => $this->getEventValue('end_date'), + ], $this->getEventID())) { + return FALSE; + } + return TRUE; + } + } diff --git a/www/modules/civicrm/CRM/Event/Form/ManageEvent.php b/www/modules/civicrm/CRM/Event/Form/ManageEvent.php index f6d13994d..66c9cc85c 100644 --- a/www/modules/civicrm/CRM/Event/Form/ManageEvent.php +++ b/www/modules/civicrm/CRM/Event/Form/ManageEvent.php @@ -18,6 +18,7 @@ * This class generates form components for processing Event. */ class CRM_Event_Form_ManageEvent extends CRM_Core_Form { + use CRM_Event_Form_EventFormTrait; /** * The id of the event we are processing. @@ -98,17 +99,12 @@ public function preProcess() { $this->assign('eventId', $this->_id); $this->_single = TRUE; - $eventInfo = \Civi\Api4\Event::get(FALSE) - ->addWhere('id', '=', $this->_id) - ->execute() - ->first(); - // its an update mode, do a permission check if (!CRM_Event_BAO_Event::checkPermission($this->_id, CRM_Core_Permission::EDIT)) { CRM_Core_Error::statusBounce(ts('You do not have permission to access this page.')); } - $participantListingID = $eventInfo['participant_listing_id'] ?? NULL; + $participantListingID = $this->getEventValue('participant_listing_id'); if ($participantListingID) { $participantListingURL = CRM_Utils_System::url('civicrm/event/participant', "reset=1&id={$this->_id}", @@ -117,14 +113,14 @@ public function preProcess() { } $this->assign('participantListingURL', $participantListingURL ?? NULL); $this->assign('participantListingID', $participantListingID); - $this->assign('isOnlineRegistration', CRM_Utils_Array::value('is_online_registration', $eventInfo)); + $this->assign('isOnlineRegistration', $this->getEventValue('is_online_registration')); $this->assign('id', $this->_id); } // figure out whether we’re handling an event or an event template if ($this->_id) { - $this->_isTemplate = $eventInfo['is_template'] ?? NULL; + $this->_isTemplate = $this->getEventValue('is_template'); } elseif ($this->_action & CRM_Core_Action::ADD) { $this->_isTemplate = CRM_Utils_Request::retrieve('is_template', 'Boolean', $this); @@ -136,11 +132,11 @@ public function preProcess() { $title = NULL; if ($this->_id) { if ($this->_isTemplate) { - $title = ts('Edit Event Template') . ' - ' . ($eventInfo['template_title'] ?? ''); + $title = ts('Edit Event Template') . ' - ' . ($this->getEventValue('template_title')); } else { $configureText = $this->_isRepeatingEvent ? ts('Configure Repeating Event') : ts('Configure Event'); - $title = $configureText . ' - ' . ($eventInfo['title'] ?? ''); + $title = $configureText . ' - ' . $this->getEventValue('title'); } } elseif ($this->_action & CRM_Core_Action::ADD) { @@ -180,18 +176,19 @@ public function preProcess() { } // also set up tabs - CRM_Event_Form_ManageEvent_TabHeader::build($this); + $this->build(); // Set Done button URL and breadcrumb. Templates go back to Manage Templates, // otherwise go to Manage Event for new event or ManageEventEdit if event if exists. if (!$this->_isTemplate) { - $breadCrumb = ['title' => ts('Manage Events')]; if ($this->_id) { + $breadCrumb = ['title' => ts('Configure Event')]; $breadCrumb['url'] = CRM_Utils_System::url(CRM_Utils_System::currentPath(), "action=update&reset=1&id={$this->_id}" ); } else { + $breadCrumb = ['title' => ts('Manage Events')]; $breadCrumb['url'] = CRM_Utils_System::url('civicrm/event/manage', 'reset=1' ); @@ -311,7 +308,7 @@ public function endPostProcess() { // hack for special cases. switch ($className) { case 'Event': - $attributes = $this->getVar('_attributes'); + $attributes = $this->_attributes; $subPage = CRM_Utils_Request::retrieveComponent($attributes); break; @@ -329,7 +326,7 @@ public function endPostProcess() { } CRM_Core_Session::setStatus(ts("'%1' information has been saved.", - [1 => CRM_Utils_Array::value('title', CRM_Utils_Array::value($subPage, $this->get('tabHeader')), $className)] + [1 => $this->get('tabHeader')[$subPage]['title'] ?? $className] ), $this->getTitle(), 'success'); if (CRM_Core_Component::isEnabled('CiviCampaign')) { @@ -341,7 +338,7 @@ public function endPostProcess() { } } $this->postProcessHook(); - if ($this->controller->getButtonName('submit') == "_qf_{$className}_upload_done") { + if ($this->controller->getButtonName('submit') === "_qf_{$className}_upload_done") { if ($this->_isTemplate) { CRM_Core_Session::singleton() ->pushUserContext(CRM_Utils_System::url('civicrm/admin/eventTemplate', 'reset=1')); @@ -362,7 +359,7 @@ public function endPostProcess() { * @return string */ public function getTemplateFileName() { - if ($this->controller->getPrint() || $this->getVar('_id') <= 0 || $this->_action & CRM_Core_Action::DELETE) { + if ($this->controller->getPrint() || $this->_id <= 0 || $this->_action & CRM_Core_Action::DELETE) { return parent::getTemplateFileName(); } else { @@ -391,4 +388,198 @@ public function getEventID() { return $this->_id ? (int) $this->_id : NULL; } + /** + * + * @return array + * @throws \CRM_Core_Exception + */ + private function build() { + $tabs = $this->get('tabHeader'); + if (!$tabs || empty($_GET['reset'])) { + $tabs = $this->processTab(); + $this->set('tabHeader', $tabs); + } + $this->assign('tabHeader', $tabs); + CRM_Core_Resources::singleton() + ->addScriptFile('civicrm', 'templates/CRM/common/TabHeader.js', 1, 'html-header') + ->addSetting([ + 'tabSettings' => [ + 'active' => $this->getCurrentTab($tabs), + ], + ]); + CRM_Event_Form_ManageEvent::addProfileEditScripts(); + return $tabs; + } + + /** + * @return array + * @throws Exception + */ + private function processTab() { + if ($this->getEventID() <= 0) { + return NULL; + } + + $default = [ + 'link' => NULL, + 'valid' => TRUE, + 'active' => TRUE, + 'current' => FALSE, + 'class' => 'ajaxForm', + ]; + + $tabs = []; + $tabs['settings'] = ['title' => ts('Info and Settings'), 'class' => 'ajaxForm livePage'] + $default; + $tabs['location'] = ['title' => ts('Event Location')] + $default; + // If CiviContribute is active, create the Fees tab. + if (CRM_Core_Component::isEnabled('CiviContribute')) { + $tabs['fee'] = ['title' => ts('Fees')] + $default; + } + $tabs['registration'] = ['title' => ts('Online Registration')] + $default; + // @fixme I don't understand the event permissions check here - can we just get rid of it? + $permissions = CRM_Event_BAO_Event::getAllPermissions(); + if (CRM_Core_Permission::check('administer CiviCRM data') || !empty($permissions[CRM_Core_Permission::EDIT])) { + $tabs['reminder'] = ['title' => ts('Schedule Reminders'), 'class' => 'livePage'] + $default; + } + + $tabs['friend'] = ['title' => ts('Tell a Friend')] + $default; + $tabs['pcp'] = ['title' => ts('Personal Campaigns')] + $default; + $tabs['repeat'] = ['title' => ts('Repeat')] + $default; + + // Repeat tab must refresh page when switching repeat mode so js & vars will get set-up + if (!$this->_isRepeatingEvent) { + unset($tabs['repeat']['class']); + } + + $eventID = $this->getEventID(); + if ($eventID) { + // disable tabs based on their configuration status + $sql = " +SELECT e.loc_block_id as is_location, e.is_online_registration, e.is_monetary, taf.is_active, pcp.is_active as is_pcp, sch.id as is_reminder, re.id as is_repeating_event +FROM civicrm_event e +LEFT JOIN civicrm_tell_friend taf ON ( taf.entity_table = 'civicrm_event' AND taf.entity_id = e.id ) +LEFT JOIN civicrm_pcp_block pcp ON ( pcp.entity_table = 'civicrm_event' AND pcp.entity_id = e.id ) +LEFT JOIN civicrm_action_schedule sch ON ( sch.mapping_id = %2 AND sch.entity_value = %1 ) +LEFT JOIN civicrm_recurring_entity re ON ( e.id = re.entity_id AND re.entity_table = 'civicrm_event' ) +WHERE e.id = %1 +"; + //Check if repeat is configured + CRM_Core_BAO_RecurringEntity::getParentFor($eventID, 'civicrm_event'); + $params = [ + 1 => [$eventID, 'Integer'], + 2 => [CRM_Event_ActionMapping::EVENT_NAME_MAPPING_ID, 'Integer'], + ]; + $dao = CRM_Core_DAO::executeQuery($sql, $params); + if (!$dao->fetch()) { + throw new CRM_Core_Exception('Unable to determine Event information'); + } + if (!$dao->is_location) { + $tabs['location']['valid'] = FALSE; + } + if (!$dao->is_online_registration) { + $tabs['registration']['valid'] = FALSE; + } + if (!$dao->is_monetary) { + $tabs['fee']['valid'] = FALSE; + } + if (!$dao->is_active) { + $tabs['friend']['valid'] = FALSE; + } + if (!$dao->is_pcp) { + $tabs['pcp']['valid'] = FALSE; + } + if (!$dao->is_reminder) { + $tabs['reminder']['valid'] = FALSE; + } + if (!$dao->is_repeating_event) { + $tabs['repeat']['valid'] = FALSE; + } + } + + // see if any other modules want to add any tabs + // note: status of 'valid' flag of any injected tab, needs to be taken care in the hook implementation. + CRM_Utils_Hook::tabset('civicrm/event/manage', $tabs, + ['event_id' => $eventID]); + + $fullName = $this->_name; + $className = CRM_Utils_String::getClassName($fullName); + $new = ''; + + // hack for special cases. + switch ($className) { + case 'Event': + $attributes = $this->_attributes; + $class = CRM_Utils_Request::retrieveComponent($attributes); + break; + + case 'EventInfo': + $class = 'settings'; + break; + + case 'ScheduleReminders': + $class = 'reminder'; + break; + + default: + $class = strtolower($className); + break; + } + + if (array_key_exists($class, $tabs)) { + $tabs[$class]['current'] = TRUE; + $qfKey = $this->get('qfKey'); + if ($qfKey) { + $tabs[$class]['qfKey'] = "&qfKey={$qfKey}"; + } + } + + if ($eventID) { + $reset = !empty($_GET['reset']) ? 'reset=1&' : ''; + + foreach ($tabs as $key => $value) { + if (!isset($tabs[$key]['qfKey'])) { + $tabs[$key]['qfKey'] = NULL; + } + + $action = 'update'; + if ($key === 'reminder') { + $action = 'browse'; + } + + $link = "civicrm/event/manage/{$key}"; + $query = "{$reset}action={$action}&id={$eventID}&component=event{$tabs[$key]['qfKey']}"; + + $tabs[$key]['link'] = (isset($value['link']) ? $value['link'] : + CRM_Utils_System::url($link, $query)); + } + } + + return $tabs; + } + + /** + * @param $tabs + * + * @return int|string + */ + private function getCurrentTab($tabs) { + static $current = FALSE; + + if ($current) { + return $current; + } + + if (is_array($tabs)) { + foreach ($tabs as $subPage => $pageVal) { + if (($pageVal['current'] ?? NULL) === TRUE) { + $current = $subPage; + break; + } + } + } + + $current = $current ?: 'settings'; + return $current; + } + } diff --git a/www/modules/civicrm/CRM/Event/Form/ManageEvent/EventInfo.php b/www/modules/civicrm/CRM/Event/Form/ManageEvent/EventInfo.php index c4149c3d4..dcbb2baec 100644 --- a/www/modules/civicrm/CRM/Event/Form/ManageEvent/EventInfo.php +++ b/www/modules/civicrm/CRM/Event/Form/ManageEvent/EventInfo.php @@ -20,10 +20,14 @@ * This class generates form components for processing Event. */ class CRM_Event_Form_ManageEvent_EventInfo extends CRM_Event_Form_ManageEvent { + use CRM_Custom_Form_CustomDataTrait; /** * Event type. + * * @var int + * + * @deprecated - never set. */ protected $_eventType; @@ -44,10 +48,6 @@ public function preProcess(): void { $this->set('subType', $_POST['event_type_id'] ?? ''); $this->assign('customDataSubType', $_POST['event_type_id'] ?? ''); $this->set('entityId', $entityID); - - CRM_Custom_Form_CustomData::preProcess($this, NULL, $this->_eventType, 1, 'Event', $entityID); - CRM_Custom_Form_CustomData::buildQuickForm($this); - CRM_Custom_Form_CustomData::setDefaultValues($this); } } @@ -185,6 +185,16 @@ public function buildQuickForm(): void { $this->addElement('checkbox', 'is_active', ts('Is this Event Active?')); $this->addFormRule(['CRM_Event_Form_ManageEvent_EventInfo', 'formRule']); + if ($this->isSubmitted()) { + // The custom data fields are added to the form by an ajax form. + // However, if they are not present in the element index they will + // not be available from `$this->getSubmittedValue()` in post process. + // We do not have to set defaults or otherwise render - just add to the element index. + $this->addCustomDataFieldsToForm('Event', array_filter([ + 'id' => $this->getEventID(), + 'event_type_id' => $_POST['event_type_id'], + ])); + } parent::buildQuickForm(); } @@ -225,14 +235,14 @@ public function postProcess() { $params['is_share'] ??= FALSE; $params['is_show_calendar_links'] ??= FALSE; $params['default_role_id'] ??= FALSE; - $params['id'] = $this->_id; + $params['id'] = $this->getEventID(); //merge params with defaults from templates if (!empty($params['template_id'])) { $params = array_merge(CRM_Event_BAO_Event::getTemplateDefaultValues($params['template_id']), $params); } - $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params, - $this->_id, + $params['custom'] = CRM_Core_BAO_CustomField::postProcess($this->getSubmittedValues(), + $this->getEventID(), 'Event' ); diff --git a/www/modules/civicrm/CRM/Event/Form/ManageEvent/Fee.php b/www/modules/civicrm/CRM/Event/Form/ManageEvent/Fee.php index 68e7bc02e..0c2ca1766 100644 --- a/www/modules/civicrm/CRM/Event/Form/ManageEvent/Fee.php +++ b/www/modules/civicrm/CRM/Event/Form/ManageEvent/Fee.php @@ -782,7 +782,7 @@ public function postProcess() { $params['id'] = $this->_id; // skip update of financial type in price set $params['skipFinancialType'] = TRUE; - CRM_Event_BAO_Event::add($params); + \Civi\Api4\Event::save(FALSE)->addRecord($params)->execute(); // Update tab "disabled" css class $this->ajaxResponse['tabValid'] = !empty($params['is_monetary']); diff --git a/www/modules/civicrm/CRM/Event/Form/ManageEvent/Location.php b/www/modules/civicrm/CRM/Event/Form/ManageEvent/Location.php index 90faa0cc9..53eaeb0b7 100644 --- a/www/modules/civicrm/CRM/Event/Form/ManageEvent/Location.php +++ b/www/modules/civicrm/CRM/Event/Form/ManageEvent/Location.php @@ -354,7 +354,7 @@ public function postProcess() { // Finally update Event params. $params['id'] = $this->_id; - CRM_Event_BAO_Event::add($params); + Event::save(FALSE)->addRecord($params)->execute(); // Update tab "disabled" CSS class. $this->ajaxResponse['tabValid'] = TRUE; diff --git a/www/modules/civicrm/CRM/Event/Form/ManageEvent/Registration.php b/www/modules/civicrm/CRM/Event/Form/ManageEvent/Registration.php index 9ee3ec93e..ff482f3a1 100644 --- a/www/modules/civicrm/CRM/Event/Form/ManageEvent/Registration.php +++ b/www/modules/civicrm/CRM/Event/Form/ManageEvent/Registration.php @@ -13,6 +13,7 @@ * @package CRM * @copyright CiviCRM LLC https://civicrm.org/licensing */ +use Civi\Api4\Event; /** * This class generates form components for processing Event. @@ -92,9 +93,7 @@ public function setDefaultValues() { 'entity_id' => $eventId, ]; - list($defaults['custom_pre_id'], - $defaults['custom_post'] - ) = CRM_Core_BAO_UFJoin::getUFGroupIds($ufJoinParams); + [$defaults['custom_pre_id'], $defaults['custom_post']] = CRM_Core_BAO_UFJoin::getUFGroupIds($ufJoinParams); // Get the id for the event registration profile $eventRegistrationIdParams = $eventRegistrationIdDefaults = [ @@ -133,9 +132,7 @@ public function setDefaultValues() { 'entity_id' => $eventId, ]; - list($defaults['additional_custom_pre_id'], - $defaults['additional_custom_post'] - ) = CRM_Core_BAO_UFJoin::getUFGroupIds($ufJoinAddParams); + [$defaults['additional_custom_pre_id'], $defaults['additional_custom_post']] = CRM_Core_BAO_UFJoin::getUFGroupIds($ufJoinAddParams); if (isset($defaults['additional_custom_post']) && is_numeric($defaults['additional_custom_post'])) { $defaults['additional_custom_post_id'] = $defaults['additional_custom_post']; @@ -398,7 +395,7 @@ public function buildMailBlock(&$form) { $form->registerRule('emailList', 'callback', 'emailList', 'CRM_Utils_Rule'); $attributes = CRM_Core_DAO::getAttribute('CRM_Event_DAO_Event'); $form->addYesNo('is_email_confirm', ts('Send Confirmation Email?'), NULL, NULL, ['onclick' => "return showHideByValue('is_email_confirm','','confirmEmail','block','radio',false);"]); - $form->add('textarea', 'confirm_email_text', ts('Text'), $attributes['confirm_email_text']); + $form->add('wysiwyg', 'confirm_email_text', ts('Text'), $attributes['confirm_email_text']); $form->add('text', 'cc_confirm', ts('CC Confirmation To'), CRM_Core_DAO::getAttribute('CRM_Event_DAO_Event', 'cc_confirm')); $form->addRule('cc_confirm', ts('Please enter a valid list of comma delimited email addresses'), 'emailList'); $form->add('text', 'bcc_confirm', ts('BCC Confirmation To'), CRM_Core_DAO::getAttribute('CRM_Event_DAO_Event', 'bcc_confirm')); @@ -794,11 +791,9 @@ public function postProcess() { if (!$params['is_online_registration']) { $params['is_email_confirm'] = FALSE; } - if (!empty($params['allow_selfcancelxfer'])) { - $params['selfcancelxfer_time'] = !empty($params['selfcancelxfer_time']) ? $params['selfcancelxfer_time'] : 0; - } + $params['selfcancelxfer_time'] = !empty($params['selfcancelxfer_time']) ? $params['selfcancelxfer_time'] : 0; - CRM_Event_BAO_Event::add($params); + Event::save(FALSE)->addRecord($params)->execute(); // also update the ProfileModule tables $ufJoinParams = [ diff --git a/www/modules/civicrm/CRM/Event/Form/ManageEvent/TabHeader.php b/www/modules/civicrm/CRM/Event/Form/ManageEvent/TabHeader.php index 27eba8a84..974d9f0ff 100644 --- a/www/modules/civicrm/CRM/Event/Form/ManageEvent/TabHeader.php +++ b/www/modules/civicrm/CRM/Event/Form/ManageEvent/TabHeader.php @@ -17,6 +17,8 @@ /** * Helper class to build navigation links + * + * @deprecated since 5.72 will be removed around 5.78. */ class CRM_Event_Form_ManageEvent_TabHeader { @@ -25,14 +27,17 @@ class CRM_Event_Form_ManageEvent_TabHeader { * * @return array * @throws \CRM_Core_Exception + * + * @deprecated since 5.72 will be removed around 5.78. */ public static function build(&$form) { + CRM_Core_Error::deprecatedWarning('no alternative'); $tabs = $form->get('tabHeader'); if (!$tabs || empty($_GET['reset'])) { $tabs = self::process($form); $form->set('tabHeader', $tabs); } - $form->assign_by_ref('tabHeader', $tabs); + $form->assign('tabHeader', $tabs); CRM_Core_Resources::singleton() ->addScriptFile('civicrm', 'templates/CRM/common/TabHeader.js', 1, 'html-header') ->addSetting([ @@ -49,8 +54,11 @@ public static function build(&$form) { * * @return array * @throws Exception + * + * @deprecated since 5.72 will be removed around 5.78. */ public static function process(&$form) { + CRM_Core_Error::deprecatedWarning('no alternative'); if ($form->getVar('_id') <= 0) { return NULL; } @@ -194,8 +202,11 @@ public static function process(&$form) { /** * @param CRM_Event_Form_ManageEvent $form + * + * @deprecated since 5.72 will be removed around 5.78. */ public static function reset(&$form) { + CRM_Core_Error::deprecatedWarning('no alternative'); $tabs = self::process($form); $form->set('tabHeader', $tabs); } @@ -204,8 +215,11 @@ public static function reset(&$form) { * @param $tabs * * @return int|string + * + * @deprecated since 5.72 will be removed around 5.78. */ public static function getCurrentTab($tabs) { + CRM_Core_Error::deprecatedWarning('no alternative'); static $current = FALSE; if ($current) { diff --git a/www/modules/civicrm/CRM/Event/Form/Participant.php b/www/modules/civicrm/CRM/Event/Form/Participant.php index 77bc50ef7..ff19875a6 100644 --- a/www/modules/civicrm/CRM/Event/Form/Participant.php +++ b/www/modules/civicrm/CRM/Event/Form/Participant.php @@ -18,6 +18,7 @@ use Civi\API\EntityLookupTrait; use Civi\Api4\Contribution; +use Civi\Api4\LineItem; /** * Back office participant form. @@ -27,6 +28,7 @@ class CRM_Event_Form_Participant extends CRM_Contribute_Form_AbstractEditPayment use EntityLookupTrait; use CRM_Contact_Form_ContactFormTrait; use CRM_Event_Form_EventFormTrait; + use CRM_Custom_Form_CustomDataTrait; /** * Participant ID - use getParticipantID. @@ -50,6 +52,8 @@ class CRM_Event_Form_Participant extends CRM_Contribute_Form_AbstractEditPayment * The values for the contribution db object. * * @var array + * + * @deprecated use $this->getPriceFieldMetadata() */ public $_values; @@ -149,15 +153,6 @@ class CRM_Event_Form_Participant extends CRM_Contribute_Form_AbstractEditPayment */ public $_eID = NULL; - /** - * Line Item for Price Set. - * - * @var array - * - * @deprecated - */ - public $_lineItem; - public $_online; /** @@ -180,6 +175,8 @@ class CRM_Event_Form_Participant extends CRM_Contribute_Form_AbstractEditPayment * Id of payment, if any * * @var int + * + * @internal */ public $_paymentId; @@ -211,7 +208,15 @@ class CRM_Event_Form_Participant extends CRM_Contribute_Form_AbstractEditPayment * @return int|null */ public function getEventID(): ?int { - return $this->_eventId ?: ($this->getSubmittedValue('event_id') ? (int) $this->getSubmittedValue('event_id') : NULL); + if (!$this->_eventId) { + if ($this->isFormBuilt()) { + $this->_eventId = $this->getSubmittedValue('event_id'); + } + else { + $this->_eventId = $this->getSubmitValue('event_id'); + } + } + return $this->_eventId ? (int) $this->_eventId : NULL; } /** @@ -339,42 +344,6 @@ public function preProcess() { CRM_Event_Form_EventFees::setDefaultValues($this); } - // when custom data is included in this page - if (!empty($_POST['hidden_custom'])) { - $eventId = (int) ($_POST['event_id'] ?? 0); - // Custom data of type participant role - // Note: Some earlier commits imply $_POST['role_id'] could be a comma separated string, - // not sure if that ever really happens - if (!empty($_POST['role_id'])) { - foreach ($_POST['role_id'] as $roleID) { - CRM_Custom_Form_CustomData::preProcess($this, $this->getExtendsEntityColumnID('ParticipantRole'), $roleID, 1, 'Participant', $this->_id); - CRM_Custom_Form_CustomData::buildQuickForm($this); - CRM_Custom_Form_CustomData::setDefaultValues($this); - } - } - - //custom data of type participant event - CRM_Custom_Form_CustomData::preProcess($this, $this->getExtendsEntityColumnID('ParticipantEventType'), $eventId, 1, 'Participant', $this->_id); - CRM_Custom_Form_CustomData::buildQuickForm($this); - CRM_Custom_Form_CustomData::setDefaultValues($this); - - // custom data of type participant event type - $eventTypeId = NULL; - if ($eventId) { - $eventTypeId = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $eventId, 'event_type_id', 'id'); - } - CRM_Custom_Form_CustomData::preProcess($this, $this->getExtendsEntityColumnID('ParticipantEventType'), $eventTypeId, - 1, 'Participant', $this->_id - ); - CRM_Custom_Form_CustomData::buildQuickForm($this); - CRM_Custom_Form_CustomData::setDefaultValues($this); - - //custom data of type participant, ( we 'null' to reset subType and subName) - CRM_Custom_Form_CustomData::preProcess($this, 'null', 'null', 1, 'Participant', $this->_id); - CRM_Custom_Form_CustomData::buildQuickForm($this); - CRM_Custom_Form_CustomData::setDefaultValues($this); - } - // CRM-4395, get the online pending contribution id. $this->_onlinePendingContributionId = NULL; if (!$this->_mode && $this->_id && ($this->_action & CRM_Core_Action::UPDATE)) { @@ -492,10 +461,6 @@ public function setDefaultValues(): array { } } - $submittedRole = $this->getElementValue('role_id'); - if (!empty($submittedRole[0])) { - $roleID = $submittedRole[0]; - } $submittedEvent = $this->getElementValue('event_id'); if (!empty($submittedEvent[0])) { $eventID = $submittedEvent[0]; @@ -577,7 +542,20 @@ public function buildQuickForm() { $this->assign('partiallyPaidStatusId', $partiallyPaidStatusId); if ($this->isOverloadFeesMode()) { - return $this->buildEventFeeForm($this); + $this->buildEventFeeForm($this); + return; + } + + if ($this->isSubmitted()) { + // The custom data fields are added to the form by an ajax form. + // However, if they are not present in the element index they will + // not be available from `$this->getSubmittedValue()` in post process. + // We do not have to set defaults or otherwise render - just add to the element index. + $this->addCustomDataFieldsToForm('Participant', array_filter([ + 'event_id' => $this->getEventID(), + 'role_id' => $_POST['role_id'] ?? NULL, + 'id' => $this->getParticipantID(), + ])); } //need to assign custom data type to the template @@ -820,6 +798,9 @@ public static function formRule($values, $files, $self) { * Process the form submission. */ public function postProcess() { + if ($this->getPriceSetID()) { + $this->getOrder()->setPriceSelectionFromUnfilteredInput($this->getSubmittedValues()); + } $statusMsg = $this->submit($this->getSubmittedValues()); CRM_Core_Session::setStatus($statusMsg, ts('Saved'), 'success'); $session = CRM_Core_Session::singleton(); @@ -846,6 +827,15 @@ public function postProcess() { "reset=1&action=add&context={$this->_context}&cid={$this->_contactId}" )); } + elseif ($this->_contactId) { + // Refresh other tabs with related data + $this->ajaxResponse['updateTabs'] = [ + '#tab_activity' => TRUE, + ]; + if (CRM_Core_Permission::access('CiviContribute')) { + $this->ajaxResponse['updateTabs']['#tab_contribute'] = CRM_Contact_BAO_Contact::getCountComponent('contribution', $this->_contactId); + } + } } /** @@ -874,8 +864,9 @@ public function submit(array $params) { } if ($this->_isPaidEvent) { - [$lineItem, $params] = $this->preparePaidEventProcessing($params); + $params = $this->preparePaidEventProcessing($params); } + $lineItem = [$this->getPriceSetID() => $this->getLineItems()]; // @todo - stop assigning these - pass financial_trxnId in token context // and swap template to use tokens. $this->assign('credit_card_type', $this->getSubmittedValue('credit_card_type')); @@ -1097,7 +1088,7 @@ public function submit(array $params) { CRM_Event_BAO_Participant::createDiscountTrxn( $this->_eventId, $contributionParams, - NULL, + '', CRM_Price_BAO_PriceSet::parseFirstPriceSetValueIDFromParams($this->getSubmittedValues()) ); } @@ -1105,28 +1096,12 @@ public function submit(array $params) { } // also store lineitem stuff here - if ((($this->_lineItem && $this->_action & CRM_Core_Action::ADD) || - ($this->_lineItem && CRM_Core_Action::UPDATE && !$this->_paymentId)) + if ((($this->getLineItems() && $this->_action & CRM_Core_Action::ADD) || + ($this->getLineItems() && CRM_Core_Action::UPDATE && !$this->_paymentId)) ) { foreach ($this->_contactIds as $num => $contactID) { - foreach ($this->_lineItem as $key => $value) { - if (is_array($value) && $value !== 'skip') { - foreach ($value as $lineKey => $line) { - //10117 update the line items for participants if contribution amount is recorded - if ($this->isQuickConfig() && !empty($params['total_amount']) && - ($params['status_id'] != array_search('Partially paid', $participantStatus)) - ) { - $line['unit_price'] = $line['line_total'] = $params['total_amount']; - if (!empty($params['tax_amount'])) { - $line['unit_price'] = $line['unit_price'] - $params['tax_amount']; - $line['line_total'] = $line['line_total'] - $params['tax_amount']; - } - } - $lineItem[$this->_priceSetId][$lineKey] = $line; - } - CRM_Price_BAO_LineItem::processPriceSet($participants[$num]->id, $lineItem, $contributions[$num] ?? NULL, 'civicrm_participant'); - } - } + $lineItem = [$this->getPriceSetID() => $this->getLineItems()]; + CRM_Price_BAO_LineItem::processPriceSet($participants[$num]->id, $lineItem, $contributions[$num] ?? NULL, 'civicrm_participant'); } foreach ($contributions as $contribution) { if (!empty($this->getCreatePaymentParams())) { @@ -1239,9 +1214,10 @@ public function buildEventFeeForm($form) { if ($form->_isPaidEvent) { $form->addElement('hidden', 'hidden_feeblock', 1); } - - $eventfullMsg = CRM_Event_BAO_Participant::eventFullMessage($form->_eventId, $this->getParticipantID()); - $form->addElement('hidden', 'hidden_eventFullMsg', $eventfullMsg, ['id' => 'hidden_eventFullMsg']); + if ($this->getEventValue('max_participants') !== NULL) { + $eventfullMsg = CRM_Event_BAO_Participant::eventFullMessage($form->_eventId, $this->getParticipantID()); + } + $form->addElement('hidden', 'hidden_eventFullMsg', $eventfullMsg ?? NULL, ['id' => 'hidden_eventFullMsg']); } if ($form->_isPaidEvent) { @@ -1251,7 +1227,7 @@ public function buildEventFeeForm($form) { //retrieve custom information $this->_values = []; $this->_values['line_items'] = CRM_Price_BAO_LineItem::getLineItems($this->_id, 'participant'); - self::initEventFee($form, $this->getPriceSetID()); + $this->initEventFee($this->getPriceSetID()); if ($form->_context === 'standalone' || $form->_context === 'participant') { $discountedEvent = CRM_Core_BAO_Discount::getOptionGroup($event['id'], 'civicrm_event'); if (is_array($discountedEvent)) { @@ -1275,14 +1251,7 @@ public function buildEventFeeForm($form) { $form->assign('priceSet', $form->_priceSet ?? NULL); } else { - $this->buildAmount($form, $form->getDiscountID(), $this->getPriceSetID()); - } - $lineItem = []; - $totalTaxAmount = 0; - if (!CRM_Utils_System::isNull($form->_values['line_items'] ?? NULL)) { - foreach ($form->_values['line_items'] as $key => $value) { - $totalTaxAmount = $value['tax_amount'] + $totalTaxAmount; - } + $this->buildAmount(); } $discounts = []; if (!empty($form->_values['discount'])) { @@ -1300,26 +1269,13 @@ public function buildEventFeeForm($form) { ['class' => "crm-select2"] ); } - if (CRM_Financial_BAO_FinancialType::isACLFinancialTypeStatus() - && empty($form->_values['fee']) - && ($_REQUEST['snippet'] ?? NULL) == CRM_Core_Smarty::PRINT_NOFORM - ) { - CRM_Core_Session::setStatus(ts('You do not have all the permissions needed for this page.'), 'Permission Denied', 'error'); - return FALSE; - } CRM_Core_Payment_Form::buildPaymentForm($form, $form->_paymentProcessor, FALSE, TRUE, self::getDefaultPaymentInstrumentId()); if (!$form->_mode) { $form->addElement('checkbox', 'record_contribution', ts('Record Payment?'), NULL, ['onclick' => "return showHideByValue('record_contribution','','payment_information','table-row','radio',false);"] ); - // Check permissions for financial type first - if (CRM_Financial_BAO_FinancialType::isACLFinancialTypeStatus()) { - CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes($financialTypes, $form->_action); - } - else { - $financialTypes = CRM_Contribute_BAO_Contribution::buildOptions('financial_type_id', 'create'); - } + $financialTypes = CRM_Contribute_BAO_Contribution::buildOptions('financial_type_id', 'create'); $form->add('select', 'financial_type_id', ts('Financial Type'), @@ -1372,7 +1328,7 @@ public function buildEventFeeForm($form) { $form->add('select', 'from_email_address', ts('Receipt From'), $form->getAvailableFromEmails()['from_email_id']); - $form->add('textarea', 'receipt_text', ts('Confirmation Message')); + $form->add('wysiwyg', 'receipt_text', ts('Confirmation Message')); // Retrieve the name and email of the contact - form will be the TO for receipt email ( only if context is not standalone) if ($form->_context !== 'standalone') { @@ -1394,27 +1350,37 @@ public function buildEventFeeForm($form) { /** * Initiate event fee. * - * @param self $form * @param int|null $priceSetId * ID of the price set in use. * * @internal function has had several recent signature changes & is expected to be eventually removed. */ - private static function initEventFee($form, $priceSetId): void { + private function initEventFee($priceSetId): void { if (!$priceSetId) { CRM_Core_Error::deprecatedWarning('this should not be reachable'); return; } + $this->_priceSet = $this->getOrder()->getPriceSetMetadata(); + $this->_priceSet['fields'] = $this->order->getPriceFieldsMetadata(); + $this->_values['fee'] = $this->getPriceFieldMetaData(); + $this->set('priceSet', $this->_priceSet); + } - $priceSet = CRM_Price_BAO_PriceSet::getSetDetail($priceSetId, NULL, FALSE); - $form->_priceSet = $priceSet[$priceSetId] ?? NULL; - $form->_values['fee'] = $form->_priceSet['fields'] ?? NULL; - $form->set('priceSet', $form->_priceSet); - - $eventFee = $form->_values['fee'] ?? NULL; - if (!is_array($eventFee) || empty($eventFee)) { - $form->_values['fee'] = []; - } + /** + * Get price field metadata. + * + * The returned value is an array of arrays where each array + * is an id-keyed price field and an 'options' key has been added to that + * arry for any options. + * + * @api This function will not change in a minor release and is supported for + * use outside of core. This annotation / external support for properties + * is only given where there is specific test cover. + * + * @return array + */ + public function getPriceFieldMetaData(): array { + return $this->order->getPriceFieldsMetadata(); } /** @@ -1450,9 +1416,6 @@ protected function preparePaidEventProcessing($params): array { if (!isset($lineItem[0])) { $lineItem[0] = []; } - CRM_Price_BAO_PriceSet::processAmount($this->_values['fee'], - $params, $lineItem[0] - ); //CRM-11529 for quick config backoffice transactions //when financial_type_id is passed in form, update the //lineitems with the financial type selected in form @@ -1464,26 +1427,12 @@ protected function preparePaidEventProcessing($params): array { } } - $params['fee_level'] = $params['amount_level']; - if ($this->isQuickConfig() && !empty($params['total_amount']) && - $params['status_id'] != array_search('Partially paid', $participantStatus) - ) { - $params['fee_amount'] = $params['total_amount']; - } - else { - //fix for CRM-3086 - $params['fee_amount'] = $params['amount']; - } + $params['fee_level'] = $this->getOrder()->getAmountLevel(); + $params['fee_amount'] = $this->getOrder()->getTotalAmount(); + $params['amount'] = $this->getOrder()->getTotalAmount(); } - if (isset($params['priceSetId'])) { - if (!empty($lineItem[0])) { - $this->set('lineItem', $lineItem); - $this->_lineItem = $lineItem; - } - } - - return [$lineItem, $params]; + return $params; } /** @@ -1579,7 +1528,7 @@ protected function processContribution($result, $contactID, // create contribution record $contribution = CRM_Contribute_BAO_Contribution::add($contribParams); // CRM-11124 - CRM_Event_BAO_Participant::createDiscountTrxn($this->getEventID(), $contribParams, NULL, CRM_Price_BAO_PriceSet::parseFirstPriceSetValueIDFromParams($this->getSubmittedValues())); + CRM_Event_BAO_Participant::createDiscountTrxn($this->getEventID(), $contribParams, '', CRM_Price_BAO_PriceSet::parseFirstPriceSetValueIDFromParams($this->getSubmittedValues())); $transaction->commit(); @@ -1610,10 +1559,16 @@ protected function addParticipant($contactID) { 'note' => $this->getSubmittedValue('note'), 'is_test' => $this->isTest(), ]; - $order = $this->getOrder(); - if ($order) { - $participantParams['fee_level'] = $order->getAmountLevel(); - $participantParams['fee_amount'] = $order->getTotalAmount(); + if (!$this->getParticipantID() || !$this->getContributionID()) { + // For new registrations, or existing ones with no contribution, + // fill in fee detail. For existing + // registrations with a contribution the user will have the option to + // change the fees via a different form. + $order = $this->getOrder(); + if ($order) { + $participantParams['fee_level'] = $order->getAmountLevel(); + $participantParams['fee_amount'] = $order->getTotalAmount(); + } } $participantParams['discount_id'] = $this->getSubmittedValue('discount_id'); $participant = CRM_Event_BAO_Participant::create($participantParams); @@ -1638,19 +1593,35 @@ protected function addParticipant($contactID) { * 2) _paymentID is the contribution id. * * @return bool + * @throws \CRM_Core_Exception */ protected function isPaymentOnExistingContribution(): bool { - return ($this->getParticipantID() && $this->_action & CRM_Core_Action::UPDATE) && $this->_paymentId; + return (bool) $this->getExistingContributionID(); } /** - * @return false|int + * @return null|int + * @throws \CRM_Core_Exception */ - protected function getExistingContributionID() { - if ($this->isPaymentOnExistingContribution()) { - return (int) $this->_paymentId; - } - return FALSE; + protected function getExistingContributionID(): ?int { + if (!$this->getParticipantID()) { + return NULL; + } + if ($this->isDefined('ExistingContribution')) { + return $this->lookup('ExistingContribution', 'id'); + } + // CRM-12615 - Get payment information from the primary registration if relevant. + $participantID = $this->getParticipantValue('registered_by_id') ?: $this->getParticipantID(); + $lineItem = LineItem::get(FALSE) + ->addWhere('entity_table', '=', 'civicrm_participant') + ->addWhere('entity_id', '=', $participantID) + ->addWhere('contribution_id', 'IS NOT NULL') + ->execute()->first(); + if (empty($lineItem)) { + return NULL; + } + $this->define('Contribution', 'ExistingContribution', ['id' => $lineItem['contribution_id']]); + return $lineItem['contribution_id']; } /** @@ -1703,7 +1674,7 @@ protected function storePaymentCreateParams(array $params): void { 'total_amount' => $params['total_amount'], 'is_send_contribution_notification' => FALSE, 'payment_instrument_id' => $params['payment_instrument_id'], - 'trxn_date' => $params['receive_date'] ?? date('Y-m-d'), + 'trxn_date' => $params['receive_date'] ?: date('Y-m-d'), 'trxn_id' => $params['trxn_id'], 'pan_truncation' => $params['pan_truncation'] ?? '', 'card_type_id' => $params['card_type_id'] ?? '', @@ -1860,7 +1831,7 @@ protected function sendReceipts($params, array $participants): array { 'PDFFilename' => ts('confirmation') . '.pdf', 'modelProps' => [ 'participantID' => $participantID, - 'userEnteredText' => $this->getSubmittedValue('receipt_text'), + 'userEnteredHTML' => $this->getSubmittedValue('receipt_text'), 'eventID' => $params['event_id'], 'contributionID' => $contributionID, ], @@ -2069,7 +2040,6 @@ protected function initializeOrder(): void { $this->submittableMoneyFields[] = 'price_' . $priceField['id']; } } - $this->order->setPriceSelectionFromUnfilteredInput($this->getSubmittedValues()); } /** @@ -2100,37 +2070,13 @@ public function getInvoiceID(): string { * @internal function is not currently called by any extentions in our civi * 'universe' and is not supported for such use. Signature has changed & will * change again. - * - * @param self $form - * Form object. - * @param int|null $discountId - * Discount id for the event. - * @param int|null $priceSetID - * - * @throws \CRM_Core_Exception */ - private function buildAmount($form, $discountId, $priceSetID): void { - $feeFields = $form->_values['fee'] ?? NULL; - - //check for discount. - $discountedFee = $form->_values['discount'] ?? NULL; - if (is_array($discountedFee) && !empty($discountedFee)) { - if (!$discountId) { - $form->_discountId = $discountId = CRM_Core_BAO_Discount::findSet($form->_eventId, 'civicrm_event'); - } - if ($discountId) { - $feeFields = &$form->_values['discount'][$discountId]; - } - } - - //its time to call the hook. - CRM_Utils_Hook::buildAmount('event', $form, $feeFields); - + private function buildAmount(): void { //build the priceset fields. // This is probably not required now - normally loaded from event .... - $form->add('hidden', 'priceSetId', $priceSetID); + $this->add('hidden', 'priceSetId', $this->getPriceSetID()); - foreach ($feeFields as $field) { + foreach ($this->getPriceFieldMetaData() as $field) { // public AND admin visibility fields are included for back-office registration and back-office change selections $fieldId = $field['id']; $elementName = 'price_' . $fieldId; @@ -2147,7 +2093,7 @@ private function buildAmount($form, $discountId, $priceSetID): void { //soft suppress required rule when option is full. if (!empty($options)) { //build the element. - CRM_Price_BAO_PriceField::addQuickFormElement($form, + CRM_Price_BAO_PriceField::addQuickFormElement($this, $elementName, $fieldId, FALSE, @@ -2157,8 +2103,7 @@ private function buildAmount($form, $discountId, $priceSetID): void { ); } } - $form->_priceSet['id'] ??= $priceSetID; - $form->assign('priceSet', $form->_priceSet); + $this->assign('priceSet', $this->_priceSet); } } diff --git a/www/modules/civicrm/CRM/Event/Form/ParticipantFeeSelection.php b/www/modules/civicrm/CRM/Event/Form/ParticipantFeeSelection.php index 9a9ca9527..9654da90e 100644 --- a/www/modules/civicrm/CRM/Event/Form/ParticipantFeeSelection.php +++ b/www/modules/civicrm/CRM/Event/Form/ParticipantFeeSelection.php @@ -62,6 +62,8 @@ class CRM_Event_Form_ParticipantFeeSelection extends CRM_Core_Form { protected $contributionAmt = NULL; + private CRM_Financial_BAO_Order $order; + public function preProcess() { $this->_fromEmails = CRM_Event_BAO_Event::getFromEmailIds($this->getEventID()); @@ -120,17 +122,70 @@ public function getContributionID(): ?int { return $this->contributionID ?: NULL; } + protected function getOrder(): CRM_Financial_BAO_Order { + if (!isset($this->order)) { + $this->initializeOrder(); + } + return $this->order; + } + + protected function initializeOrder(): void { + $this->order = new CRM_Financial_BAO_Order(); + $this->order->setPriceSetID($this->getPriceSetID()); + $this->order->setIsExcludeExpiredFields($this->_action !== CRM_Core_Action::UPDATE); + $this->order->setForm($this); + foreach ($this->getPriceFieldMetaData() as $priceField) { + if ($priceField['html_type'] === 'Text') { + $this->submittableMoneyFields[] = 'price_' . $priceField['id']; + } + } + } + + /** + * Get the form context. + * + * This is important for passing to the buildAmount hook as CiviDiscount checks it. + * + * @return string + */ + public function getFormContext(): string { + return 'event'; + } + + /** + * Get price field metadata. + * + * The returned value is an array of arrays where each array + * is an id-keyed price field and an 'options' key has been added to that + * arry for any options. + * + * @api This function will not change in a minor release and is supported for + * use outside of core. This annotation / external support for properties + * is only given where there is specific test cover. + * + * @return array + */ + public function getPriceFieldMetaData(): array { + if (!empty($this->_values['fee'])) { + return $this->_values['fee']; + } + if (!empty($this->_priceSet['fields'])) { + return $this->_priceSet['fields']; + } + return $this->order->getPriceFieldsMetadata(); + } + /** * Set default values for the form. * * @return array */ public function setDefaultValues() { - $params = ['id' => $this->_participantId]; + $params = ['id' => $this->getParticipantID()]; CRM_Event_BAO_Participant::getValues($params, $defaults, $ids); - $priceSetValues = CRM_Event_Form_EventFees::setDefaultPriceSet($this->_participantId, $this->_eventId, FALSE); + $priceSetValues = $this->getPriceSetDefaults(); $priceFieldId = (array_keys($this->_values['fee'])); if (!empty($priceSetValues)) { $defaults[$this->_participantId] = array_merge($defaults[$this->_participantId], $priceSetValues); @@ -143,17 +198,88 @@ public function setDefaultValues() { } } } - $this->assign('totalAmount', CRM_Utils_Array::value('fee_amount', $defaults[$this->_participantId])); + $this->assign('totalAmount', $defaults[$this->_participantId]['fee_amount'] ?? NULL); if ($this->_action == CRM_Core_Action::UPDATE) { $fee_level = $defaults[$this->_participantId]['fee_level']; CRM_Event_BAO_Participant::fixEventLevel($fee_level); $this->assign('fee_level', $fee_level); - $this->assign('fee_amount', CRM_Utils_Array::value('fee_amount', $defaults[$this->_participantId])); + $this->assign('fee_amount', $defaults[$this->_participantId]['fee_amount'] ?? NULL); } $defaults = $defaults[$this->_participantId]; return $defaults; } + /** + * This function sets the default values for price set. + * + * @return array + */ + private function getPriceSetDefaults() { + $defaults = []; + $participantID = $this->getParticipantID(); + + // use line items for setdefault price set fields, CRM-4090 + $lineItems[$participantID] = CRM_Price_BAO_LineItem::getLineItems($participantID, 'participant', FALSE, FALSE); + + if (is_array($lineItems[$participantID]) && + !CRM_Utils_System::isNull($lineItems[$participantID]) + ) { + + $priceFields = $htmlTypes = $optionValues = []; + foreach ($lineItems[$participantID] as $lineId => $items) { + $priceFieldId = $items['price_field_id'] ?? NULL; + $priceOptionId = $items['price_field_value_id'] ?? NULL; + if ($priceFieldId && $priceOptionId) { + $priceFields[$priceFieldId][] = $priceOptionId; + } + } + + if (empty($priceFields)) { + return $defaults; + } + + // get all price set field html types. + $sql = ' +SELECT id, html_type + FROM civicrm_price_field + WHERE id IN (' . implode(',', array_keys($priceFields)) . ')'; + $fieldDAO = CRM_Core_DAO::executeQuery($sql); + while ($fieldDAO->fetch()) { + $htmlTypes[$fieldDAO->id] = $fieldDAO->html_type; + } + + foreach ($lineItems[$participantID] as $lineId => $items) { + $fieldId = $items['price_field_id']; + $htmlType = $htmlTypes[$fieldId] ?? NULL; + if (!$htmlType) { + continue; + } + + if ($htmlType === 'Text') { + $defaults["price_{$fieldId}"] = $items['qty']; + } + else { + $fieldOptValues = $priceFields[$fieldId] ?? NULL; + if (!is_array($fieldOptValues)) { + continue; + } + + foreach ($fieldOptValues as $optionId) { + if ($htmlType === 'CheckBox') { + $defaults["price_{$fieldId}"][$optionId] = TRUE; + } + else { + $defaults["price_{$fieldId}"] = $optionId; + break; + } + } + } + } + } + + return $defaults; + } + /** * Build form. * @@ -177,8 +303,10 @@ public function buildQuickForm() { $this->_values = []; $this->_values['line_items'] = CRM_Price_BAO_LineItem::getLineItems($this->_participantId, 'participant'); - CRM_Event_Form_Registration::initEventFee($this, $this->_action !== CRM_Core_Action::UPDATE, $this->getPriceSetID()); - CRM_Event_Form_Registration_Register::buildAmount($this, TRUE, NULL, $this->getPriceSetID()); + $this->_priceSet = $this->getOrder()->getPriceSetMetadata(); + $this->setPriceFieldMetaData($this->order->getPriceFieldsMetadata()); + self::initializeEventFee($this, $this->_action !== CRM_Core_Action::UPDATE, $this->getPriceSetID()); + $this->buildAmount(TRUE, NULL, $this->getPriceSetID()); if (!CRM_Utils_System::isNull($this->_values['line_items'] ?? NULL)) { $lineItem[] = $this->_values['line_items']; @@ -230,6 +358,159 @@ public function buildQuickForm() { $this->addFormRule(['CRM_Event_Form_ParticipantFeeSelection', 'formRule'], $this); } + /** + * Set price field metadata. + * + * @param array $metadata + */ + protected function setPriceFieldMetaData(array $metadata): void { + $this->_values['fee'] = $this->_priceSet['fields'] = $metadata; + } + + /** + * Build the radio/text form elements for the amount field + * + * @internal function is not currently called by any extentions in our civi + * 'universe' and is not supported for such use. Signature has changed & will + * change again. + * + * @param int|null $discountId + * Discount id for the event. + * @param int|null $priceSetID + * + * @throws \CRM_Core_Exception + */ + protected function buildAmount($discountId, $priceSetID) { + $form = $this; + $feeFields = $form->_values['fee'] ?? NULL; + + if (is_array($feeFields)) { + $form->_feeBlock = &$form->_values['fee']; + } + + //check for discount. + $discountedFee = $form->_values['discount'] ?? NULL; + if (is_array($discountedFee) && !empty($discountedFee)) { + if (!$discountId) { + $form->_discountId = $discountId = CRM_Core_BAO_Discount::findSet($form->_eventId, 'civicrm_event'); + } + if ($discountId) { + $form->_feeBlock = &$form->_values['discount'][$discountId]; + } + } + if (!is_array($form->_feeBlock)) { + $form->_feeBlock = []; + } + + //its time to call the hook. + CRM_Utils_Hook::buildAmount('event', $form, $form->_feeBlock); + + //format price set fields across option full. + $form->formatFieldsForOptionFull(); + // This is probably not required now - normally loaded from event .... + $form->add('hidden', 'priceSetId', $priceSetID); + + foreach ($form->_feeBlock as $field) { + $fieldId = $field['id']; + $elementName = 'price_' . $fieldId; + + $isRequire = $field['is_required'] ?? NULL; + + //user might modified w/ hook. + $options = $field['options'] ?? NULL; + + if (!is_array($options)) { + continue; + } + + $optionFullIds = CRM_Utils_Array::value('option_full_ids', $field, []); + + //soft suppress required rule when option is full. + if (!empty($optionFullIds) && (count($options) == count($optionFullIds))) { + $isRequire = FALSE; + } + if (!empty($options)) { + //build the element. + CRM_Price_BAO_PriceField::addQuickFormElement($form, + $elementName, + $fieldId, + FALSE, + $isRequire, + NULL, + $options, + $optionFullIds + ); + } + } + $form->_priceSet['id'] ??= $priceSetID; + $form->assign('priceSet', $form->_priceSet); + } + + /** + * + */ + private function formatFieldsForOptionFull(): void { + $form = $this; + $priceSet = $form->get('priceSet'); + $priceSetId = $form->get('priceSetId'); + $defaultPricefieldIds = []; + if (!empty($form->_values['line_items'])) { + foreach ($form->_values['line_items'] as $lineItem) { + $defaultPricefieldIds[] = $lineItem['price_field_value_id']; + } + } + if (!$priceSetId || + !is_array($priceSet) || + empty($priceSet) || empty($priceSet['optionsMaxValueTotal']) + ) { + return; + } + + //get the current price event price set options count. + $currentOptionsCount = $this->getPriceOptionCount(); + $recordedOptionsCount = CRM_Event_BAO_Participant::priceSetOptionsCount($form->_eventId, []); + $optionFullTotalAmount = 0; + $currentParticipantNo = (int) substr($form->_name, 12); + foreach ($form->_feeBlock as & $field) { + $optionFullIds = []; + if (!is_array($field['options'])) { + continue; + } + foreach ($field['options'] as & $option) { + $optId = $option['id']; + $maxValue = $option['max_value'] ?? 0; + $dbTotalCount = $recordedOptionsCount[$optId] ?? 0; + $currentTotalCount = $currentOptionsCount[$optId] ?? 0; + + $totalCount = $currentTotalCount + $dbTotalCount; + $isFull = FALSE; + if ($maxValue && + (($totalCount >= $maxValue) && + (empty($form->_lineItem[$currentParticipantNo][$optId]['price_field_id']) || $dbTotalCount >= $maxValue)) + ) { + $isFull = TRUE; + $optionFullIds[$optId] = $optId; + if ($field['html_type'] !== 'Select') { + if (in_array($optId, $defaultPricefieldIds)) { + $optionFullTotalAmount += $option['amount'] ?? 0; + } + } + else { + if (!empty($defaultPricefieldIds) && in_array($optId, $defaultPricefieldIds)) { + unset($optionFullIds[$optId]); + } + } + } + $option['is_full'] = $isFull; + $option['total_option_count'] = $dbTotalCount + $currentTotalCount; + } + + //finally get option ids in. + $field['option_full_ids'] = $optionFullIds; + } + $form->assign('optionFullTotalAmount', $optionFullTotalAmount); + } + /** * Get the discount ID. * @@ -369,7 +650,7 @@ private function emailReceipt(array $params): void { } $this->assign('totalAmount', $this->contributionAmt); - $this->assign('checkNumber', CRM_Utils_Array::value('check_number', $params)); + $this->assign('checkNumber', $params['check_number'] ?? NULL); } $this->assign('register_date', $params['register_date']); @@ -490,4 +771,113 @@ public function getContactID(): ?int { return $this->_contactId; } + /** + * Initiate event fee. + * + * Formerly shared function. + * + * @internal function has had several recent signature changes & is expected to be eventually removed. + */ + private function initializeEventFee(): void { + $priceSetId = $this->getPriceSetID(); + $form = $this; + //get the price set fields participant count. + //get option max value info. + $optionsMaxValueTotal = 0; + + if (!empty($form->_priceSet['fields'])) { + foreach ($form->_priceSet['fields'] as $field) { + foreach ($field['options'] as $option) { + $maxVal = $option['max_value'] ?? 0; + $optionsMaxValueTotal += $maxVal; + } + } + } + + $form->_priceSet['optionsMaxValueTotal'] = $optionsMaxValueTotal; + + $form->set('priceSet', $form->_priceSet); + + $eventFee = $form->_values['fee'] ?? NULL; + if (!is_array($eventFee) || empty($eventFee)) { + $form->_values['fee'] = []; + } + } + + /** + * Calculate total count for each price set options. + * + * - currently selected by user. + * + * @return array + * array of each option w/ count total. + */ + private function getPriceOptionCount() { + $form = $this; + $params = $form->get('params'); + $priceSet = $form->get('priceSet'); + $priceSetId = $form->get('priceSetId'); + + $optionsCount = []; + if (!$priceSetId || + !is_array($priceSet) || + empty($priceSet) || + !is_array($params) || + empty($params) + ) { + return $optionsCount; + } + + $priceSetFields = $priceMaxFieldDetails = []; + // @todo - replace this line with if ($this->getOrder()->isUseParticipantCount()) { + // (pending https://github.com/civicrm/civicrm-core/pull/29249 being merged) + if (!empty($priceSet['optionsCountTotal'])) { + $priceSetFields = $priceSet['optionsCountDetails']['fields']; + } + + if (!empty($priceSet['optionsMaxValueTotal'])) { + $priceMaxFieldDetails = $priceSet['optionsMaxValueDetails']['fields']; + } + + $addParticipantNum = substr($form->_name, 12); + foreach ($params as $pCnt => $values) { + if ($values == 'skip' || + $pCnt === $addParticipantNum + ) { + continue; + } + + foreach ($values as $valKey => $value) { + if (strpos($valKey, 'price_') === FALSE) { + continue; + } + + $priceFieldId = substr($valKey, 6); + if (!$priceFieldId || + !is_array($value) || + !(array_key_exists($priceFieldId, $priceSetFields) || array_key_exists($priceFieldId, $priceMaxFieldDetails)) + ) { + continue; + } + + foreach ($value as $optId => $optVal) { + if (($priceSet['fields'][$priceFieldId]['html_type'] ?? NULL) === 'Text') { + $currentCount = $optVal; + } + else { + $currentCount = 1; + } + + if (isset($priceSetFields[$priceFieldId]) && isset($priceSetFields[$priceFieldId]['options'][$optId])) { + $currentCount = $priceSetFields[$priceFieldId]['options'][$optId] * $optVal; + } + + $optionsCount[$optId] = $currentCount + ($optionsCount[$optId] ?? 0); + } + } + } + + return $optionsCount; + } + } diff --git a/www/modules/civicrm/CRM/Event/Form/ParticipantView.php b/www/modules/civicrm/CRM/Event/Form/ParticipantView.php index c87906b92..7e739ac2f 100644 --- a/www/modules/civicrm/CRM/Event/Form/ParticipantView.php +++ b/www/modules/civicrm/CRM/Event/Form/ParticipantView.php @@ -20,6 +20,7 @@ * */ class CRM_Event_Form_ParticipantView extends CRM_Core_Form { + use CRM_Event_Form_EventFormTrait; public $useLivePageJS = TRUE; @@ -30,7 +31,7 @@ class CRM_Event_Form_ParticipantView extends CRM_Core_Form { */ public function preProcess() { $values = $ids = []; - $participantID = CRM_Utils_Request::retrieve('id', 'Positive', $this, TRUE); + $participantID = $this->getParticipantID(); $contactID = CRM_Utils_Request::retrieve('cid', 'Positive', $this, TRUE); $params = ['id' => $participantID]; @@ -58,9 +59,14 @@ public function preProcess() { $this->assign('hasPayment', $paymentId); $this->assign('componentId', $participantID); $this->assign('component', 'event'); + $parentParticipantID = $this->getParticipantValue('registered_by_id'); + $this->assign('participant_registered_by_id', $parentParticipantID); + // Check if this is a primaryParticipant (registered for others) and retrieve additional participants if true (CRM-4859) + if (CRM_Event_BAO_Participant::isPrimaryParticipant($this->getParticipantID())) { + $additionalParticipants = CRM_Event_BAO_Participant::getAdditionalParticipants($this->getParticipantID()); + } + $this->assign('additionalParticipants', $additionalParticipants ?? NULL); - $parentParticipantID = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Participant', - $participantID, 'registered_by_id'); $this->assign('parentHasPayment', !$parentParticipantID ? NULL : CRM_Core_DAO::getFieldValue('CRM_Event_DAO_ParticipantPayment', $parentParticipantID, 'id', 'participant_id' )); @@ -93,37 +99,16 @@ public function preProcess() { ); } - if ($values[$participantID]['is_test']) { - $values[$participantID]['status'] = CRM_Core_TestEntity::appendTestText($values[$participantID]['status']); - } - - // Get Note - $noteValue = CRM_Core_BAO_Note::getNote($participantID, 'civicrm_participant'); - - $values[$participantID]['note'] = array_values($noteValue); + $this->assign('status', $this->getParticipantValue('status_id:label') . ($this->getParticipantValue('is_test') ? ' ' . ts('(test)') : '')); + $this->assign('note', array_values(CRM_Core_BAO_Note::getNote($participantID, 'civicrm_participant'))); // Get Line Items $lineItem = CRM_Price_BAO_LineItem::getLineItems($participantID); + $this->assign('lineItem', [$lineItem]); - if (!CRM_Utils_System::isNull($lineItem)) { - $values[$participantID]['lineItem'][] = $lineItem; - } - - $values[$participantID]['totalAmount'] = $values[$participantID]['fee_amount'] ?? NULL; - - // Get registered_by contact ID and display_name if participant was registered by someone else (CRM-4859) - if (!empty($values[$participantID]['participant_registered_by_id'])) { - $values[$participantID]['registered_by_contact_id'] = CRM_Core_DAO::getFieldValue("CRM_Event_DAO_Participant", - $values[$participantID]['participant_registered_by_id'], - 'contact_id', 'id' - ); - $values[$participantID]['registered_by_display_name'] = CRM_Contact_BAO_Contact::displayName($values[$participantID]['registered_by_contact_id']); - } - - // Check if this is a primaryParticipant (registered for others) and retrieve additional participants if true (CRM-4859) - if (CRM_Event_BAO_Participant::isPrimaryParticipant($participantID)) { - $values[$participantID]['additionalParticipants'] = CRM_Event_BAO_Participant::getAdditionalParticipants($participantID); - } + // Assign registered_by contact ID and display_name if participant was registered by someone else (CRM-4859) + $this->assign('registered_by_display_name', $this->getParticipantValue('registered_by_id.contact_id.display_name')); + $this->assign('registered_by_contact_id', $this->getParticipantValue('registered_by_id.contact_id')); // get the option value for custom data type $customDataType = CRM_Core_OptionGroup::values('custom_data_type', FALSE, FALSE, FALSE, NULL, 'name'); @@ -155,17 +140,9 @@ public function preProcess() { CRM_Core_BAO_CustomGroup::buildCustomDataView($this, $finalTree, FALSE, NULL, NULL, NULL, $participantID); $eventTitle = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $values[$participantID]['event_id'], 'title'); //CRM-7150, show event name on participant view even if the event is disabled - if (empty($values[$participantID]['event'])) { - $values[$participantID]['event'] = $eventTitle; - } - - //do check for campaigns - $campaignId = $values[$participantID]['campaign_id'] ?? NULL; - if ($campaignId) { - $campaigns = CRM_Campaign_BAO_Campaign::getCampaigns($campaignId); - $values[$participantID]['campaign'] = $campaigns[$campaignId]; - } - + $this->assign('event', $eventTitle); + $this->assign('campaign', $this->getParticipantValue('campaign_id:label')); + // @todo - this assign makes it really hard to see what is being assigned - do individual assigns. $this->assign($values[$participantID]); // add viewed participant to recent items list @@ -181,40 +158,36 @@ public function preProcess() { } if (CRM_Core_Permission::check('delete in CiviEvent')) { $recentOther['deleteUrl'] = CRM_Utils_System::url('civicrm/participant/delete', - "reset=1&id={$values[$participantID]['id']}}" + "reset=1&id={$values[$participantID]['id']}" ); } - $participantRoles = CRM_Event_PseudoConstant::participantRole(); $displayName = CRM_Contact_BAO_Contact::displayName($values[$participantID]['contact_id']); $participantCount = []; - $totalTaxAmount = 0; + $totalTaxAmount = $totalAmount = 0; foreach ($lineItem as $k => $v) { if (CRM_Utils_Array::value('participant_count', $lineItem[$k]) > 0) { $participantCount[] = $lineItem[$k]['participant_count']; } $totalTaxAmount = $v['tax_amount'] + $totalTaxAmount; - } - if (Civi::settings()->get('invoicing')) { - $this->assign('totalTaxAmount', $totalTaxAmount); - } + $totalAmount += $v['line_total']; + } + $this->assign('currency', $this->getParticipantValue('fee_currency')); + // It would be more correct to assign totalTaxAmount & TotalAmount + // from the order object - however, that assumes a contribution exists & there is this + // we have this weird possibility of line items against a participant record with + // no contribution attached to it - maybe we have eliminated this? But I have a nasty feeling about + // webform. + $this->assign('totalTaxAmount', $totalTaxAmount ?? NULL); + $this->assign('totalAmount', $totalAmount); $this->assign('pricesetFieldsCount', $participantCount); $this->assign('displayName', $displayName); // omitting contactImage from title for now since the summary overlay css doesn't work outside of our crm-container $this->setTitle(ts('View Event Registration for') . ' ' . $displayName); - - $roleId = $values[$participantID]['role_id'] ?? NULL; - $title = $displayName . ' (' . ($participantRoles[$roleId] ?? '') . ' - ' . $eventTitle . ')'; - - $sep = CRM_Core_DAO::VALUE_SEPARATOR; - $viewRoles = []; - foreach (explode($sep, $values[$participantID]['role_id']) as $k => $v) { - $viewRoles[] = $participantRoles[$v]; - } - $values[$participantID]['role_id'] = implode(', ', $viewRoles); - $this->assign('role', $values[$participantID]['role_id']); + $this->assign('role', implode(',', $this->getParticipantValue('role_id:label'))); // add Participant to Recent Items + $title = $displayName . ' (' . implode(',', $this->getParticipantValue('role_id:label')) . ' - ' . $eventTitle . ')'; CRM_Utils_Recent::add($title, $url, $values[$participantID]['id'], @@ -225,6 +198,17 @@ public function preProcess() { ); } + /** + * Get id of participant being acted on. + * + * @api This function will not change in a minor release and is supported for + * use outside of core. This annotation / external support for properties + * is only given where there is specific test cover. + */ + public function getParticipantID(): int { + return (int) CRM_Utils_Request::retrieve('id', 'Positive', $this, TRUE); + } + /** * Build the form object. * diff --git a/www/modules/civicrm/CRM/Event/Form/Registration.php b/www/modules/civicrm/CRM/Event/Form/Registration.php index e31f111ab..6005a1a4d 100644 --- a/www/modules/civicrm/CRM/Event/Form/Registration.php +++ b/www/modules/civicrm/CRM/Event/Form/Registration.php @@ -32,6 +32,17 @@ class CRM_Event_Form_Registration extends CRM_Core_Form { */ public $_eventId; + private CRM_Financial_BAO_Order $order; + + private array $optionsCount; + + protected function getOrder(): CRM_Financial_BAO_Order { + if (!isset($this->order)) { + $this->initializeOrder(); + } + return $this->order; + } + /** * Get the selected Event ID. * @@ -54,6 +65,38 @@ public function getEventID(): int { return $this->_eventId; } + /** + * Set price field metadata. + * + * @param array $metadata + */ + protected function setPriceFieldMetaData(array $metadata): void { + $this->_values['fee'] = $this->_priceSet['fields'] = $metadata; + } + + /** + * Get price field metadata. + * + * The returned value is an array of arrays where each array + * is an id-keyed price field and an 'options' key has been added to that + * arry for any options. + * + * @api This function will not change in a minor release and is supported for + * use outside of core. This annotation / external support for properties + * is only given where there is specific test cover. + * + * @return array + */ + public function getPriceFieldMetaData(): array { + if (!empty($this->_values['fee'])) { + return $this->_values['fee']; + } + if (!empty($this->_priceSet['fields'])) { + return $this->_priceSet['fields']; + } + return $this->order->getPriceFieldsMetadata(); + } + /** * The array of ids of all the participant we are processing. * @@ -224,9 +267,16 @@ public function preProcess() { $this->_availableRegistrations = $this->get('availableRegistrations'); $this->_participantIDS = $this->get('participantIDs'); + // Required for currency formatting in the JS layer + // this is a temporary fix intended to resolve a regression quickly + // And assigning moneyFormat for js layer formatting + // will only work until that is done. + // https://github.com/civicrm/civicrm-core/pull/19151 + $this->assign('moneyFormat', CRM_Utils_Money::format(1234.56, $this->getCurrency())); + //check if participant allow to walk registration wizard. $this->_allowConfirmation = $this->get('allowConfirmation'); - + $this->assign('currency', $this->getCurrency()); // check for Approval $this->_requireApproval = $this->get('requireApproval'); @@ -238,12 +288,24 @@ public function preProcess() { $this->showPaymentOnConfirm = (in_array($this->_eventId, \Civi::settings()->get('event_show_payment_on_confirm')) || in_array('all', \Civi::settings()->get('event_show_payment_on_confirm'))); $this->assign('showPaymentOnConfirm', $this->showPaymentOnConfirm); + $priceSetID = $this->getPriceSetID(); + if ($priceSetID) { + $this->_priceSet = $this->getOrder()->getPriceSetMetadata(); + $this->setPriceFieldMetaData($this->getOrder()->getPriceFieldsMetadata()); + $this->assign('quickConfig', $this->isQuickConfig()); + } - if (!$this->_values) { + // If there is money involved the call to setPriceFieldMetaData will have set the key 'fee'. + // 'fee' is a duplicate of other properties but some places still refer to it + // $this->getPriceFieldsMetadata() is the recommended interaction. + if (!$this->_values || count($this->_values) === 1) { // get all the values from the dao object $this->_values = $this->_fields = []; - + // ensure 'fee' is set since it just got wiped out + if ($this->getPriceSetID()) { + $this->setPriceFieldMetaData($this->getOrder()->getPriceFieldsMetadata()); + } //retrieve event information $params = ['id' => $this->getEventID()]; CRM_Event_BAO_Event::retrieve($params, $this->_values['event']); @@ -309,7 +371,7 @@ public function preProcess() { $priceSetID = $this->getPriceSetID(); if ($priceSetID) { $this->_values['line_items'] = CRM_Price_BAO_LineItem::getLineItems($this->_participantId, 'participant'); - self::initEventFee($this, TRUE, $priceSetID); + $this->initEventFee(); //fix for non-upgraded price sets.CRM-4256. if (isset($this->_isPaidEvent)) { @@ -318,10 +380,9 @@ public function preProcess() { else { $isPaidEvent = $this->_values['event']['is_monetary'] ?? NULL; } - if ($isPaidEvent && empty($this->_values['fee'])) { + if ($isPaidEvent && empty($this->getPriceFieldMetaData())) { CRM_Core_Error::statusBounce(ts('No Fee Level(s) or Price Set is configured for this event.
Click CiviEvent >> Manage Event >> Configure >> Event Fees to configure the Fee Level(s) or Price Set for this event.', [1 => CRM_Utils_System::url('civicrm/event/manage/fee', 'reset=1&action=update&id=' . $this->_eventId)])); } - $this->assign('quickConfig', $this->isQuickConfig()); } // get the profile ids @@ -376,7 +437,7 @@ public function preProcess() { ); $this->set('availableRegistrations', $this->_availableRegistrations); } - $this->assign_by_ref('paymentProcessor', $this->_paymentProcessor); + $this->assign('paymentProcessor', $this->_paymentProcessor); // check if this is a paypal auto return and redirect accordingly if (CRM_Core_Payment::paypalRedirect($this->_paymentProcessor)) { @@ -597,64 +658,62 @@ public function buildCustom($id, $name) { /** * Initiate event fee. * - * @param \CRM_Event_Form_Registration|\CRM_Event_Form_ParticipantFeeSelection $form - * @param bool $doNotIncludeExpiredFields - * See CRM-16456. - * @param int|null $priceSetId - * ID of the price set in use. - * * @internal function has had several recent signature changes & is expected to be eventually removed. */ - public static function initEventFee($form, $doNotIncludeExpiredFields, $priceSetId): void { - if (!$priceSetId) { - CRM_Core_Error::deprecatedWarning('this should not be reachable'); - return; - } - - $priceSet = CRM_Price_BAO_PriceSet::getSetDetail($priceSetId, NULL, $doNotIncludeExpiredFields); - $form->_priceSet = $priceSet[$priceSetId] ?? NULL; - $form->_values['fee'] = $form->_priceSet['fields'] ?? NULL; - + private function initEventFee(): void { //get the price set fields participant count. //get option count info. - $form->_priceSet['optionsCountTotal'] = CRM_Price_BAO_PriceSet::getPricesetCount($priceSetId); - if ($form->_priceSet['optionsCountTotal']) { + if ($this->getOrder()->isUseParticipantCount()) { $optionsCountDetails = []; - if (!empty($form->_priceSet['fields'])) { - foreach ($form->_priceSet['fields'] as $field) { + if (!empty($this->_priceSet['fields'])) { + foreach ($this->_priceSet['fields'] as $field) { foreach ($field['options'] as $option) { $count = $option['count'] ?? 0; $optionsCountDetails['fields'][$field['id']]['options'][$option['id']] = $count; } } } - $form->_priceSet['optionsCountDetails'] = $optionsCountDetails; + $this->_priceSet['optionsCountDetails'] = $optionsCountDetails; } //get option max value info. $optionsMaxValueTotal = 0; $optionsMaxValueDetails = []; - if (!empty($form->_priceSet['fields'])) { - foreach ($form->_priceSet['fields'] as $field) { + if ($this->isMaxValueValidationRequired()) { + foreach ($this->getPriceFieldMetaData() as $field) { foreach ($field['options'] as $option) { $maxVal = $option['max_value'] ?? 0; $optionsMaxValueDetails['fields'][$field['id']]['options'][$option['id']] = $maxVal; $optionsMaxValueTotal += $maxVal; } } + $this->_priceSet['optionsMaxValueDetails'] = $optionsMaxValueDetails; } + $this->set('priceSet', $this->_priceSet); + } - $form->_priceSet['optionsMaxValueTotal'] = $optionsMaxValueTotal; - if ($optionsMaxValueTotal) { - $form->_priceSet['optionsMaxValueDetails'] = $optionsMaxValueDetails; + protected function initializeOrder(): void { + $this->order = new CRM_Financial_BAO_Order(); + $this->order->setPriceSetID($this->getPriceSetID()); + $this->order->setIsExcludeExpiredFields(TRUE); + $this->order->setForm($this); + foreach ($this->getPriceFieldMetaData() as $priceField) { + if ($priceField['html_type'] === 'Text') { + $this->submittableMoneyFields[] = 'price_' . $priceField['id']; + } } - $form->set('priceSet', $form->_priceSet); + } - $eventFee = $form->_values['fee'] ?? NULL; - if (!is_array($eventFee) || empty($eventFee)) { - $form->_values['fee'] = []; - } + /** + * Get the form context. + * + * This is important for passing to the buildAmount hook as CiviDiscount checks it. + * + * @return string + */ + public function getFormContext(): string { + return 'event'; } /** @@ -779,7 +838,7 @@ protected function addParticipant(&$form, $contactID) { $participantParams = [ 'id' => $params['participant_id'] ?? NULL, 'contact_id' => $contactID, - 'event_id' => $form->_eventId ? $form->_eventId : $params['event_id'], + 'event_id' => $form->_eventId ?: $params['event_id'], 'status_id' => $params['participant_status'] ?? 1, 'role_id' => $params['participant_role_id'] ?? CRM_Event_BAO_Participant::getDefaultRoleID(), 'register_date' => ($registerDate) ? $registerDate : date('YmdHis'), @@ -841,31 +900,188 @@ protected function addParticipant(&$form, $contactID) { return $participant; } + /** + * Get the array of price field value IDs on the form that 'count' as + * full. + * + * The criteria for full is slightly confusing as it has an exclusion around + * select fields if they are the default - or something... + * + * @param array $field + * + * @return array + * @throws \CRM_Core_Exception + */ + protected function getOptionFullPriceFieldValues(array $field): array { + $optionFullIds = []; + foreach ($field['options'] ?? [] as &$option) { + if ($this->isOptionFullID($option, $field)) { + $optionFullIds[$option['id']] = $option['id']; + } + } + return $optionFullIds; + } + + /** + * Is the option a 'full ID'. + * + * It is not clear why this is different to the is_full calculation + * but it is used in a less narrow context, around validation. + * + * Ideally figure it out & update this doc block. + * + * @param array $option + * @param array $field + * + * @return bool + * @throws \CRM_Core_Exception + */ + private function isOptionFullID(array $option, array $field) : bool { + $fieldId = $option['price_field_id']; + $currentParticipantNo = (int) substr($this->_name, 12); + $defaultPricefieldIds = []; + if (!empty($this->_values['line_items'])) { + foreach ($this->_values['line_items'] as $lineItem) { + $defaultPricefieldIds[] = $lineItem['price_field_value_id']; + } + } + $formattedPriceSetDefaults = []; + if (!empty($this->_allowConfirmation) && (isset($this->_pId) || isset($this->_additionalParticipantId))) { + $participantId = $this->_pId ?? $this->_additionalParticipantId; + $pricesetDefaults = CRM_Event_Form_EventFees::setDefaultPriceSet($participantId, + $this->getEventID() + ); + // modify options full to respect the selected fields + // options on confirmation. + $formattedPriceSetDefaults = self::formatPriceSetParams($this, $pricesetDefaults); + } + + //get the current price event price set options count. + $currentOptionsCount = $this->getPriceSetOptionCount(); + $optId = $option['id']; + $count = $option['count'] ?? 0; + $currentTotalCount = $currentOptionsCount[$optId] ?? 0; + $isOptionFull = FALSE; + $totalCount = $currentTotalCount + $this->getUsedSeatsCount($optId); + if ($option['max_value'] && + (($totalCount >= $option['max_value']) && + (empty($this->_lineItem[$currentParticipantNo][$optId]['price_field_id']) || $this->getUsedSeatsCount($optId) >= $option['max_value'])) + ) { + $isOptionFull = TRUE; + if ($field['html_type'] === 'Select') { + if (!empty($defaultPricefieldIds) && in_array($optId, $defaultPricefieldIds)) { + $isOptionFull = FALSE; + } + } + } + //here option is not full, + //but we don't want to allow participant to increase + //seats at the time of re-walking registration. + if ($count && + !empty($this->_allowConfirmation) && + !empty($formattedPriceSetDefaults) + ) { + if (empty($formattedPriceSetDefaults["price_{$fieldId}"]) || empty($formattedPriceSetDefaults["price_{$fieldId}"][$optId])) { + $isOptionFull = TRUE; + } + } + return $isOptionFull; + } + + /** + * Should this option be disabled on the basis of being full. + * + * Note there is another full calculation that is slightly different for + * ... reasons? When we figure out what those are we can update this. + * + * @param array $option + * + * @return bool + * @throws \CRM_Core_Exception + */ + protected function getIsOptionFull(array $option): bool { + $isFull = FALSE; + $currentParticipantNo = (int) substr($this->_name, 12); + $formattedPriceSetDefaults = []; + $maxValue = $option['max_value'] ?? 0; + $priceFieldValueID = $option['id']; + //get the current price event price set options count. + $currentOptionsCount = $this->getPriceSetOptionCount(); + $currentTotalCount = $currentOptionsCount[$priceFieldValueID] ?? 0; + + $totalCount = $currentTotalCount + $this->getUsedSeatsCount($priceFieldValueID); + if (!empty($form->_allowConfirmation) && (isset($form->_pId) || isset($form->_additionalParticipantId))) { + $participantId = $form->_pId ?? $form->_additionalParticipantId; + $pricesetDefaults = CRM_Event_Form_EventFees::setDefaultPriceSet($participantId, + $this->getEventID() + ); + // modify options full to respect the selected fields + // options on confirmation. + $formattedPriceSetDefaults = self::formatPriceSetParams($form, $pricesetDefaults); + } + $count = $option['count'] ?? 0; + if ($maxValue && + (($totalCount >= $maxValue) && + (empty($this->_lineItem[$currentParticipantNo][$priceFieldValueID]['price_field_id']) || $this->getUsedSeatsCount($priceFieldValueID) >= $maxValue)) + ) { + $isFull = TRUE; + } + //here option is not full, + //but we don't want to allow participant to increase + //seats at the time of re-walking registration. + if ($count && + !empty($this->_allowConfirmation) && + !empty($formattedPriceSetDefaults) + ) { + if (empty($formattedPriceSetDefaults["price_{$option['price_field_id']}"]) || empty($formattedPriceSetDefaults["price_{$option['price_field_id']}"][$priceFieldValueID])) { + $isFull = TRUE; + } + } + return $isFull; + } + + /** + * Get the used seat count for the price value option + * + * @return int + * @throws \CRM_Core_Exception + */ + protected function getUsedSeatsCount(int $priceFieldValueID) : int { + $skipParticipants = []; + if (!empty($this->_allowConfirmation) && (isset($this->_pId) || isset($this->_additionalParticipantId))) { + // to skip current registered participants fields option count on confirmation. + $skipParticipants[] = $this->_participantId; + if (!empty($this->_additionalParticipantIds)) { + $skipParticipants = array_merge($skipParticipants, $this->_additionalParticipantIds); + } + } + $this->optionsCount = CRM_Event_BAO_Participant::priceSetOptionsCount($this->getEventID(), $skipParticipants); + return $this->optionsCount[$priceFieldValueID] ?? 0; + } + /** * Calculate the total participant count as per params. * - * @param CRM_Core_Form $form * @param array $params * User params. * @param bool $skipCurrent * * @return int */ - public static function getParticipantCount(&$form, $params, $skipCurrent = FALSE) { + protected function getParticipantCount($params, $skipCurrent = FALSE) { $totalCount = 0; + $form = $this; if (!is_array($params) || empty($params)) { return $totalCount; } $priceSetId = $form->get('priceSetId'); $addParticipantNum = substr($form->_name, 12); - $priceSetFields = $priceSetDetails = []; + $priceSetFields = []; $hasPriceFieldsCount = FALSE; if ($priceSetId) { $priceSetDetails = $form->get('priceSet'); - if (isset($priceSetDetails['optionsCountTotal']) - && $priceSetDetails['optionsCountTotal'] - ) { + if ($form->getOrder()->isUseParticipantCount()) { $hasPriceFieldsCount = TRUE; $priceSetFields = $priceSetDetails['optionsCountDetails']['fields']; } @@ -950,7 +1166,7 @@ public function getParticipantID(): ?int { * * Convert price set each param as an array. * - * @param CRM_Core_Form $form + * @param self $form * @param array $params * An array of user submitted params. * @@ -997,13 +1213,11 @@ public static function formatPriceSetParams(&$form, $params) { * * - currently selected by user. * - * @param CRM_Core_Form $form - * Form object. - * * @return array * array of each option w/ count total. */ - public static function getPriceSetOptionCount(&$form) { + protected function getPriceSetOptionCount() { + $form = $this; $params = $form->get('params'); $priceSet = $form->get('priceSet'); $priceSetId = $form->get('priceSetId'); @@ -1019,11 +1233,11 @@ public static function getPriceSetOptionCount(&$form) { } $priceSetFields = $priceMaxFieldDetails = []; - if (!empty($priceSet['optionsCountTotal'])) { + if ($form->getOrder()->isUseParticipantCount()) { $priceSetFields = $priceSet['optionsCountDetails']['fields']; } - if (!empty($priceSet['optionsMaxValueTotal'])) { + if ($this->isMaxValueValidationRequired()) { $priceMaxFieldDetails = $priceSet['optionsMaxValueDetails']['fields']; } @@ -1227,30 +1441,39 @@ public static function resetSubmittedValue($elementName, $optionIds, &$form) { } } + /** + * Get the number of available spaces in the given event. + * + * @internal this is a transitional function to handle this form's + * odd behaviour whereby sometimes the fetched value is text. We need to wean + * the places that access it off this... + * + * @return int + * + * @throws \CRM_Core_Exception + */ + protected function getAvailableSpaces(): int { + // non numeric would be 'event full text'.... + return is_numeric($this->_availableRegistrations) ? (int) $this->_availableRegistrations : 0; + } + /** * Validate price set submitted params for price option limit. * * User should select at least one price field option. * - * @param CRM_Core_Form $form * @param array $params + * @param int $priceSetId + * @param array $priceSetDetails * * @return array */ - public static function validatePriceSet(&$form, $params) { + protected function validatePriceSet(array $params, $priceSetId, $priceSetDetails) { $errors = []; $hasOptMaxValue = FALSE; if (!is_array($params) || empty($params)) { return $errors; } - - $currentParticipantNum = substr($form->_name, 12); - if (!$currentParticipantNum) { - $currentParticipantNum = 0; - } - - $priceSetId = $form->get('priceSetId'); - $priceSetDetails = $form->get('priceSet'); if ( !$priceSetId || !is_array($priceSetDetails) || @@ -1261,24 +1484,16 @@ public static function validatePriceSet(&$form, $params) { $optionsCountDetails = $optionsMaxValueDetails = []; if ( - isset($priceSetDetails['optionsMaxValueTotal']) - && $priceSetDetails['optionsMaxValueTotal'] + $this->isMaxValueValidationRequired() ) { $hasOptMaxValue = TRUE; $optionsMaxValueDetails = $priceSetDetails['optionsMaxValueDetails']['fields']; } - if ( - isset($priceSetDetails['optionsCountTotal']) - && $priceSetDetails['optionsCountTotal'] - ) { + + if ($this->getOrder()->isUseParticipantCount()) { $hasOptCount = TRUE; $optionsCountDetails = $priceSetDetails['optionsCountDetails']['fields']; } - $feeBlock = $form->_feeBlock; - - if (empty($feeBlock)) { - $feeBlock = $priceSetDetails['fields']; - } $optionMaxValues = $fieldSelected = []; foreach ($params as $pNum => $values) { @@ -1292,7 +1507,7 @@ public static function validatePriceSet(&$form, $params) { } $priceFieldId = substr($valKey, 6); $noneOptionValueSelected = FALSE; - if (!$feeBlock[$priceFieldId]['is_required'] && $value == 0) { + if (!$this->getPriceFieldMetaData()[$priceFieldId]['is_required'] && $value == 0) { $noneOptionValueSelected = TRUE; } @@ -1310,7 +1525,7 @@ public static function validatePriceSet(&$form, $params) { } foreach ($value as $optId => $optVal) { - if (($feeBlock[$priceFieldId]['html_type'] ?? NULL) === 'Text') { + if (($this->getPriceFieldMetaData()[$priceFieldId]['html_type'] ?? NULL) === 'Text') { $currentMaxValue = $optVal; } else { @@ -1338,10 +1553,9 @@ public static function validatePriceSet(&$form, $params) { //validate for option max value. foreach ($optionMaxValues as $fieldId => $values) { - $options = $feeBlock[$fieldId]['options'] ?? []; foreach ($values as $optId => $total) { $optMax = $optionsMaxValueDetails[$fieldId]['options'][$optId]; - $opDbCount = $options[$optId]['db_total_count'] ?? 0; + $opDbCount = $this->getUsedSeatsCount($optId); $total += $opDbCount; if ($optMax && ($total > $optMax)) { if ($opDbCount && ($opDbCount >= $optMax)) { @@ -1636,6 +1850,8 @@ private function sendMails($params, $registerByID, array $participantCount) { $primaryContactId = $this->get('primaryContactId'); //build an array of custom profile and assigning it to template. + // @todo - don't call buildCustomProfile to get additionalParticipants. + // CRM_Event_BAO_Participant::getAdditionalParticipantIds is a better fit. $additionalIDs = CRM_Event_BAO_Event::buildCustomProfile($registerByID, NULL, $primaryContactId, $isTest, TRUE ); @@ -1750,14 +1966,12 @@ public function isQuickConfig(): bool { * Rather historic - might have unneeded stuff * * @return string + * @throws \CRM_Core_Exception */ public function getCurrency() { - $currency = $this->_values['currency'] ?? NULL; - // For event forms, currency is in a different spot - if (empty($currency)) { - $currency = CRM_Utils_Array::value('currency', CRM_Utils_Array::value('event', $this->_values)); - } + $currency = $this->getEventValue('currency'); if (empty($currency)) { + // Is this valid? It comes from previously shared code. $currency = CRM_Utils_Request::retrieveValue('currency', 'String'); } // @todo If empty there is a problem - we should probably put in a deprecation notice @@ -1765,4 +1979,150 @@ public function getCurrency() { return $currency; } + /** + * Build the radio/text form elements for the amount field + * + * @internal function is not currently called by any extentions in our civi + * 'universe' and is not supported for such use. Signature has changed & will + * change again. + * + * @throws \CRM_Core_Exception + */ + protected function buildAmount() { + $form = $this; + $priceSetID = $this->_priceSetId; + $required = TRUE; + $discountId = NULL; + $feeFields = $this->getPriceFieldMetaData(); + + //check for discount. + $discountedFee = $form->_values['discount'] ?? NULL; + if (is_array($discountedFee) && !empty($discountedFee)) { + CRM_Core_Error::deprecatedWarning('code believed to be unreachable.'); + if (!$discountId) { + $form->_discountId = $discountId = CRM_Core_BAO_Discount::findSet($form->_eventId, 'civicrm_event'); + } + if ($discountId) { + $feeFields = &$form->_values['discount'][$discountId]; + } + } + + //reset required if participant is skipped. + $button = substr($form->controller->getButtonName(), -4); + if ($required && $button === 'skip') { + $required = FALSE; + } + + //build the priceset fields. + if ($priceSetID) { + + // This is probably not required now - normally loaded from event .... + $form->add('hidden', 'priceSetId', $priceSetID); + + // CRM-14492 Admin price fields should show up on event registration if user has 'administer CiviCRM' permissions + $adminFieldVisible = CRM_Core_Permission::check('administer CiviCRM data'); + $hideAdminValues = !CRM_Core_Permission::check('edit event participants'); + + foreach ($feeFields as $field) { + // public AND admin visibility fields are included for back-office registration and back-office change selections + if (($field['visibility'] ?? NULL) === 'public' || + (($field['visibility'] ?? NULL) === 'admin' && $adminFieldVisible == TRUE) + ) { + $fieldId = $field['id']; + $elementName = 'price_' . $fieldId; + + $isRequire = $field['is_required'] ?? NULL; + if ($button === 'skip') { + $isRequire = FALSE; + } + + //user might modified w/ hook. + $options = $field['options'] ?? NULL; + + if (!is_array($options)) { + continue; + } + if ($hideAdminValues) { + $publicVisibilityID = CRM_Price_BAO_PriceField::getVisibilityOptionID('public'); + $adminVisibilityID = CRM_Price_BAO_PriceField::getVisibilityOptionID('admin'); + + foreach ($options as $key => $currentOption) { + $optionVisibility = CRM_Utils_Array::value('visibility_id', $currentOption, $publicVisibilityID); + if ($optionVisibility == $adminVisibilityID) { + unset($options[$key]); + } + } + } + + $optionFullIds = $this->getOptionFullPriceFieldValues($field); + + //soft suppress required rule when option is full. + if (!empty($optionFullIds) && (count($options) == count($optionFullIds))) { + $isRequire = FALSE; + } + foreach ($options as $option) { + $options[$option['id']]['is_full'] = $this->getIsOptionFull($option); + } + if (!empty($options)) { + //build the element. + CRM_Price_BAO_PriceField::addQuickFormElement($form, + $elementName, + $fieldId, + FALSE, + $isRequire, + NULL, + $options, + $optionFullIds + ); + } + } + } + } + else { + // Is this reachable? + // Noisy deprecation notice added in Sep 2023 (in previous code location). + CRM_Core_Error::deprecatedWarning('code believed to be unreachable'); + $eventFeeBlockValues = $elements = $elementJS = []; + foreach ($feeFields as $fee) { + if (is_array($fee)) { + + //CRM-7632, CRM-6201 + $totalAmountJs = NULL; + $eventFeeBlockValues['amount_id_' . $fee['amount_id']] = $fee['value']; + $elements[$fee['amount_id']] = CRM_Utils_Money::format($fee['value']) . ' ' . $fee['label']; + $elementJS[$fee['amount_id']] = $totalAmountJs; + } + } + $form->assign('eventFeeBlockValues', json_encode($eventFeeBlockValues)); + + $form->_defaults['amount'] = $form->_values['event']['default_fee_id'] ?? NULL; + $element = &$form->addRadio('amount', ts('Event Fee(s)'), $elements, [], '
', FALSE, $elementJS); + if (isset($form->_online) && $form->_online) { + $element->freeze(); + } + if ($required) { + $form->addRule('amount', ts('Fee Level is a required field.'), 'required'); + } + } + } + + /** + * Is there a price field value configured with a maximum value. + * + * If so there will need to be a check to ensure the number used does not + * exceed it. + * + * @return bool + */ + protected function isMaxValueValidationRequired(): bool { + foreach ($this->getPriceFieldMetaData() as $field) { + foreach ($field['options'] as $priceValueOption) { + if ($priceValueOption['max_value']) { + return TRUE; + } + } + } + return FALSE; + } + } diff --git a/www/modules/civicrm/CRM/Event/Form/Registration/AdditionalParticipant.php b/www/modules/civicrm/CRM/Event/Form/Registration/AdditionalParticipant.php index bfe53c695..806e39200 100644 --- a/www/modules/civicrm/CRM/Event/Form/Registration/AdditionalParticipant.php +++ b/www/modules/civicrm/CRM/Event/Form/Registration/AdditionalParticipant.php @@ -94,7 +94,7 @@ public function getUFGroupIDs() { */ public function preProcess() { parent::preProcess(); - + $this->addExpectedSmartyVariable('additionalCustomPost'); $participantNo = substr($this->_name, 12); //lets process in-queue participants. @@ -104,14 +104,11 @@ public function preProcess() { $participantCnt = $participantNo + 1; $this->assign('formId', $participantNo); - $this->_params = []; $this->_params = $this->get('params'); $participantTot = $this->_params[0]['additional_participants'] + 1; $skipCount = count(array_keys($this->_params, "skip")); - if ($skipCount) { - $this->assign('skipCount', $skipCount); - } + $this->assign('skipCount', $skipCount); $this->setTitle(ts('Register Participant %1 of %2', [1 => $participantCnt, 2 => $participantTot])); //CRM-4320, hack to check last participant. @@ -142,12 +139,12 @@ public function setDefaultValues() { } } if ($this->_priceSetId) { - foreach ($this->_feeBlock as $key => $val) { + foreach ($this->getPriceFieldMetaData() as $key => $val) { if (empty($val['options'])) { continue; } - $optionsFull = CRM_Utils_Array::value('option_full_ids', $val, []); + $optionsFull = $this->getOptionFullPriceFieldValues($val); foreach ($val['options'] as $keys => $values) { if ($values['is_default'] && !in_array($keys, $optionsFull)) { if ($val['html_type'] === 'CheckBox') { @@ -219,8 +216,9 @@ public function buildQuickForm() { $button = substr($this->controller->getButtonName(), -4); if ($this->_values['event']['is_monetary']) { - CRM_Event_Form_Registration_Register::buildAmount($this, TRUE, NULL, $this->_priceSetId); + $this->buildAmount(TRUE, NULL, $this->_priceSetId); } + $this->assign('priceSet', $this->_priceSet); //Add pre and post profiles on the form. foreach (['pre', 'post'] as $keys) { @@ -247,7 +245,7 @@ public function buildQuickForm() { if ($this->_lastParticipant || $pricesetFieldsCount) { //get the participant total. - $processedCnt = self::getParticipantCount($this, $this->_params, TRUE); + $processedCnt = $this->getParticipantCount($this->_params, TRUE); } if (!$this->_allowConfirmation && !empty($this->_params[0]['bypass_payment']) && @@ -325,8 +323,10 @@ public function buildQuickForm() { } } + // Assign false & maybe overwrite with TRUE below. + $this->assign('allowGroupOnWaitlist', FALSE); // for priceset with count - if ($pricesetFieldsCount && !empty($this->_values['event']['has_waitlist']) && + if ($pricesetFieldsCount && $this->getEventValue('has_waitlist') && !$this->_allowConfirmation ) { @@ -498,10 +498,10 @@ public static function formRule($fields, $files, $self) { //format current participant params. $allParticipantParams[$addParticipantNum] = self::formatPriceSetParams($self, $fields); - $totalParticipants = self::getParticipantCount($self, $allParticipantParams); + $totalParticipants = $self->getParticipantCount($allParticipantParams); //validate price field params. - $priceSetErrors = self::validatePriceSet($self, $allParticipantParams); + $priceSetErrors = $self->validatePriceSet($allParticipantParams, $self->get('priceSetId'), $self->get('priceSet')); $errors = array_merge($errors, CRM_Utils_Array::value($addParticipantNum, $priceSetErrors, [])); if (!$self->_allowConfirmation && @@ -658,13 +658,13 @@ public function postProcess() { ) { $this->_allowWaitlist = FALSE; //get the current page count. - $currentCount = self::getParticipantCount($this, $params); - if ($button == 'skip') { + $currentCount = $this->getParticipantCount($params); + if ($button === 'skip') { $currentCount = 'skip'; } //get the total count. - $previousCount = self::getParticipantCount($this, $this->_params, TRUE); + $previousCount = $this->getParticipantCount($this->_params, TRUE); $totalParticipants = $previousCount; if (is_numeric($currentCount)) { $totalParticipants += $currentCount; diff --git a/www/modules/civicrm/CRM/Event/Form/Registration/Confirm.php b/www/modules/civicrm/CRM/Event/Form/Registration/Confirm.php index 2714de2e4..18247b128 100644 --- a/www/modules/civicrm/CRM/Event/Form/Registration/Confirm.php +++ b/www/modules/civicrm/CRM/Event/Form/Registration/Confirm.php @@ -81,14 +81,13 @@ public function preProcess() { $this->_params[0]['is_pay_later'] = $this->get('is_pay_later'); $this->assign('is_pay_later', $this->_params[0]['is_pay_later']); $this->assign('pay_later_receipt', $this->_params[0]['is_pay_later'] ? $this->_values['event']['pay_later_receipt'] : NULL); - + $this->assign('confirm_text', $this->getEventValue('confirm_text')); CRM_Utils_Hook::eventDiscount($this, $this->_params); if (!empty($this->_params[0]['discount']) && !empty($this->_params[0]['discount']['applied'])) { $this->set('hookDiscount', $this->_params[0]['discount']); - $this->assign('hookDiscount', $this->_params[0]['discount']); } - + $this->assign('hookDiscount', $this->_params[0]['discount'] ?? ''); $this->preProcessExpress(); if ($this->_values['event']['is_monetary']) { @@ -109,7 +108,9 @@ public function preProcess() { $this->setTitle($this->_values['event']['confirm_title']); } - // Personal campaign page + // Personal campaign page. + // Unclear if this really is possible on event pages or copy & paste. + $this->assign('pcpBlock', FALSE); if ($this->_pcpId) { $params = CRM_Contribute_Form_Contribution_Confirm::processPcp($this, $this->_params[0]); $this->_params[0] = $params; @@ -219,19 +220,20 @@ public function getAction() { */ public function buildQuickForm() { $this->assignToTemplate(); + // This use of the ts function uses the legacy interpolation of the button name to avoid translations having to be re-done. + $this->assign('verifyText', !$this->_totalAmount ? ts('Click %1 to complete your registration.', [1 => ts('Register')]) : $this->getPaymentProcessorObject()->getText('eventContinueText', [])); if ($this->_values['event']['is_monetary'] && - ($this->_params[0]['amount'] || $this->_params[0]['amount'] == 0) && + (isset($this->_params[0]['amount']) && is_numeric($this->_params[0]['amount'])) && !$this->_requireApproval ) { [$taxAmount, $participantDetails, $individual, $amountArray] = $this->calculateAmounts(); - + $this->assign('totalTaxAmount', $taxAmount); $this->_amount = $amountArray; - + $this->assign('taxTerm', \Civi::settings()->get('tax_term')); if (\Civi::settings()->get('invoicing')) { - $this->assign('totalTaxAmount', $taxAmount); - $this->assign('taxTerm', \Civi::settings()->get('tax_term')); + // @todo - remove this - used to be for online event template but no longer used. $this->assign('individual', $individual); $this->set('individual', $individual); } @@ -241,9 +243,6 @@ public function buildQuickForm() { $this->assign('amounts', $amountArray); $this->assign('totalAmount', $this->_totalAmount); $this->set('totalAmount', $this->_totalAmount); - // This use of the ts function uses the legacy interpolation of the button name to avoid translations having to be re-done. - $this->assign('verifyText', !$this->_totalAmount ? ts('Click %1 to complete your registration.', [1 => ts('Register')]) : $this->getPaymentProcessorObject()->getText('eventContinueText', [])); - $showPaymentOnConfirm = (in_array($this->_eventId, \Civi::settings()->get('event_show_payment_on_confirm')) || in_array('all', \Civi::settings()->get('event_show_payment_on_confirm'))); $this->assign('showPaymentOnConfirm', $showPaymentOnConfirm); if ($showPaymentOnConfirm) { @@ -348,17 +347,17 @@ public static function formRule($fields, $files, $form) { CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/event/register', "reset=1&id={$form->getEventID()}", FALSE, NULL, FALSE, TRUE)); } } - $form->_feeBlock = $form->_values['fee']; - CRM_Event_Form_Registration_Register::formatFieldsForOptionFull($form); + if ($form->getEventValue('is_monetary')) { - if (!empty($form->_priceSetId) && - !$form->_requireApproval && !$form->_allowWaitlist + if (!empty($form->_priceSetId) && + !$form->_requireApproval && !$form->_allowWaitlist ) { - $errors = self::validatePriceSet($form, $form->_params); - if (!empty($errors)) { - CRM_Core_Session::setStatus(ts('You have been returned to the start of the registration process and any sold out events have been removed from your selections. You will not be able to continue until you review your booking and select different events if you wish.'), ts('Unfortunately some of your options have now sold out for one or more participants.'), 'error'); - CRM_Core_Session::setStatus(ts('Please note that the options which are marked or selected are sold out for participant being viewed.'), ts('Sold out:'), 'error'); - CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/event/register', "_qf_Register_display=true&qfKey={$fields['qfKey']}")); + $errors = $form->validatePriceSet($form->_params, $form->_priceSetId, $form->get('priceSet')); + if (!empty($errors)) { + CRM_Core_Session::setStatus(ts('You have been returned to the start of the registration process and any sold out events have been removed from your selections. You will not be able to continue until you review your booking and select different events if you wish.'), ts('Unfortunately some of your options have now sold out for one or more participants.'), 'error'); + CRM_Core_Session::setStatus(ts('Please note that the options which are marked or selected are sold out for participant being viewed.'), ts('Sold out:'), 'error'); + CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/event/register', "_qf_Register_display=true&qfKey={$fields['qfKey']}")); + } } } @@ -779,6 +778,8 @@ public function postProcess() { else { //build an array of cId/pId of participants + // @todo - don't call buildCustomProfile to get additionalParticipants. + // CRM_Event_BAO_Participant::getAdditionalParticipantIds is a better fit. $additionalIDs = CRM_Event_BAO_Event::buildCustomProfile($registerByID, NULL, $primaryContactId, $isTest, TRUE @@ -960,7 +961,7 @@ private function processContribution( // create contribution record $contribution = CRM_Contribute_BAO_Contribution::add($contribParams); // CRM-11124 - CRM_Event_BAO_Participant::createDiscountTrxn($form->getEventID(), $contribParams, NULL, CRM_Price_BAO_PriceSet::parseFirstPriceSetValueIDFromParams($params)); + CRM_Event_BAO_Participant::createDiscountTrxn($form->getEventID(), $contribParams, '', CRM_Price_BAO_PriceSet::parseFirstPriceSetValueIDFromParams($params)); $transaction->commit(); @@ -1139,7 +1140,7 @@ public static function updateContactFields($contactID, $params, $fields, &$form) /** * Assign Profiles to the template. * - * @param CRM_Event_Form_Registration_Confirm $form + * @param CRM_Event_Form_Registration_Confirm|\CRM_Event_Form_Registration_ThankYou $form * * @throws \CRM_Core_Exception */ @@ -1203,6 +1204,7 @@ public static function assignProfiles($form) { } $form->_fields = $profileFields; } + $form->assign('addParticipantProfile', []); if (!empty($formattedValues)) { $form->assign('primaryParticipantProfile', $formattedValues[1]); $form->set('primaryParticipantProfile', $formattedValues[1]); diff --git a/www/modules/civicrm/CRM/Event/Form/Registration/Register.php b/www/modules/civicrm/CRM/Event/Form/Registration/Register.php index c44572091..46db61b50 100644 --- a/www/modules/civicrm/CRM/Event/Form/Registration/Register.php +++ b/www/modules/civicrm/CRM/Event/Form/Registration/Register.php @@ -50,6 +50,8 @@ class CRM_Event_Form_Registration_Register extends CRM_Event_Form_Registration { * Show fee block or not. * * @var bool + * + * @deprecated */ public $_noFees; @@ -116,7 +118,7 @@ public static function getRegistrationContactID($fields, $form, $isAdditional) { $contactID = $form->getContactID(); } if (!$contactID && is_array($fields) && $fields) { - $contactID = CRM_Contact_BAO_Contact::getFirstDuplicateContact($fields, 'Individual', 'Unsupervised', [], FALSE, CRM_Utils_Array::value('dedupe_rule_group_id', $form->_values['event']), ['event_id' => CRM_Utils_Array::value('id', $form->_values['event'])]); + $contactID = CRM_Contact_BAO_Contact::getFirstDuplicateContact($fields, 'Individual', 'Unsupervised', [], FALSE, $form->_values['event']['dedupe_rule_group_id'] ?? NULL, ['event_id' => $form->_values['event']['id'] ?? NULL]); } return $contactID; } @@ -164,10 +166,9 @@ public function preProcess() { $eventFull = CRM_Event_BAO_Participant::eventFull($this->_eventId, FALSE, CRM_Utils_Array::value('has_waitlist', $this->_values['event'])); // Get payment processors if appropriate for this event - // We hide the payment fields if the event is full or requires approval, - // and the current user has not yet been approved CRM-12279 - $this->_noFees = (($eventFull || $this->_requireApproval) && !$this->_allowConfirmation); - $this->_paymentProcessors = $this->_noFees ? [] : $this->get('paymentProcessors'); + $this->_noFees = $suppressPayment = $this->isSuppressPayment(); + $this->_paymentProcessors = $suppressPayment ? [] : $this->get('paymentProcessors'); + $this->assign('suppressPaymentBlock', $suppressPayment); $this->preProcessPaymentOptions(); $this->_allowWaitlist = FALSE; @@ -189,24 +190,22 @@ public function preProcess() { // get the participant values from EventFees.php, CRM-4320 if ($this->_allowConfirmation) { - $this->eventFeeWrangling($this); + $this->eventFeeWrangling(); } } /** * This is previously shared code which is probably of little value. * - * @param CRM_Core_Form $form - * * @throws \CRM_Core_Exception */ - private function eventFeeWrangling($form) { - $form->_pId = CRM_Utils_Request::retrieve('participantId', 'Positive', $form); - $form->_discountId = CRM_Utils_Request::retrieve('discountId', 'Positive', $form); + private function eventFeeWrangling() { + $this->_pId = CRM_Utils_Request::retrieve('participantId', 'Positive', $this); + $this->_discountId = CRM_Utils_Request::retrieve('discountId', 'Positive', $this); //CRM-6907 set event specific currency. if ($this->getEventID() && - ($currency = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $form->_eventId, 'currency')) + ($currency = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $this->getEventID(), 'currency')) ) { CRM_Core_Config::singleton()->defaultCurrency = $currency; } @@ -221,7 +220,7 @@ private function eventFeeWrangling($form) { */ public function setDefaultValues() { $this->_defaults = []; - if (!$this->_allowConfirmation && $this->_requireApproval) { + if ($this->isSuppressPayment()) { $this->_defaults['bypass_payment'] = 1; } $contactID = $this->getContactID(); @@ -292,12 +291,12 @@ public function setDefaultValues() { $this->_defaults['participant_role'] = $this->_defaults['participant_role_id'] = $this->_values['event']['default_role_id']; } - if ($this->_priceSetId && !empty($this->_feeBlock)) { - foreach ($this->_feeBlock as $key => $val) { + if ($this->_priceSetId) { + foreach ($this->getPriceFieldMetaData() as $key => $val) { if (empty($val['options'])) { continue; } - $optionFullIds = CRM_Utils_Array::value('option_full_ids', $val, []); + $optionFullIds = $this->getOptionFullPriceFieldValues($val); foreach ($val['options'] as $keys => $values) { $priceFieldName = 'price_' . $values['price_field_id']; $priceFieldValue = CRM_Price_BAO_PriceSet::getPriceFieldValueFromURL($this, $priceFieldName); @@ -308,8 +307,8 @@ public function setDefaultValues() { break; } else { - if ($values['is_default'] && empty($values['is_full'])) { - if ($val['html_type'] == 'CheckBox') { + if ($values['is_default'] && !$this->getIsOptionFull($values)) { + if ($val['html_type'] === 'CheckBox') { $this->_defaults["price_{$key}"][$keys] = 1; } else { @@ -433,16 +432,10 @@ public function buildQuickForm() { $this->assign('isAdditionalParticipants', $isAdditionalParticipants); if ($this->_values['event']['is_monetary']) { - // Required for currency formatting in the JS layer - // this is a temporary fix intended to resolve a regression quickly - // And assigning moneyFormat for js layer formatting - // will only work until that is done. - // https://github.com/civicrm/civicrm-core/pull/19151 - $this->assign('moneyFormat', CRM_Utils_Money::format(1234.56)); // build amount only when needed, skip incase of event full and waitlisting is enabled // and few other conditions check preProcess() - if (!$this->_noFees) { - self::buildAmount($this, TRUE, NULL, $this->_priceSetId); + if (!$this->isSuppressPayment()) { + $this->buildAmount(); } if (!$this->showPaymentOnConfirm) { CRM_Core_Payment_ProcessorForm::buildQuickForm($this); @@ -455,7 +448,7 @@ public function buildQuickForm() { //@todo we are blocking for multiple registrations because we haven't tested $this->addCIDZeroOptions(); } - + $this->assign('priceSet', $this->_priceSet); $this->addElement('hidden', 'bypass_payment', NULL, ['id' => 'bypass_payment']); $this->assign('bypassPayment', $bypassPayment); @@ -553,248 +546,6 @@ public function buildQuickForm() { } } - /** - * Build the radio/text form elements for the amount field - * - * @internal function is not currently called by any extentions in our civi - * 'universe' and is not supported for such use. Signature has changed & will - * change again. - * - * @param CRM_Event_Form_Registration_Register $form - * Form object. - * @param bool $required - * True if you want to add formRule. - * @param int|null $discountId - * Discount id for the event. - * @param int|null $priceSetID - * - * @throws \CRM_Core_Exception - */ - public static function buildAmount($form, $required, $discountId, $priceSetID) { - $feeFields = $form->_values['fee'] ?? NULL; - - if (is_array($feeFields)) { - $form->_feeBlock = &$form->_values['fee']; - } - - //check for discount. - $discountedFee = $form->_values['discount'] ?? NULL; - if (is_array($discountedFee) && !empty($discountedFee)) { - if (!$discountId) { - $form->_discountId = $discountId = CRM_Core_BAO_Discount::findSet($form->_eventId, 'civicrm_event'); - } - if ($discountId) { - $form->_feeBlock = &$form->_values['discount'][$discountId]; - } - } - if (!is_array($form->_feeBlock)) { - $form->_feeBlock = []; - } - - //its time to call the hook. - CRM_Utils_Hook::buildAmount('event', $form, $form->_feeBlock); - - //reset required if participant is skipped. - $button = substr($form->controller->getButtonName(), -4); - if ($required && $button == 'skip') { - $required = FALSE; - } - - $className = CRM_Utils_System::getClassName($form); - - //build the priceset fields. - if ($priceSetID) { - - //format price set fields across option full. - self::formatFieldsForOptionFull($form); - // This is probably not required now - normally loaded from event .... - $form->add('hidden', 'priceSetId', $priceSetID); - - // CRM-14492 Admin price fields should show up on event registration if user has 'administer CiviCRM' permissions - $adminFieldVisible = CRM_Core_Permission::check('administer CiviCRM data'); - $hideAdminValues = !CRM_Core_Permission::check('edit event participants'); - - foreach ($form->_feeBlock as $field) { - // public AND admin visibility fields are included for back-office registration and back-office change selections - if (($field['visibility'] ?? NULL) == 'public' || - (($field['visibility'] ?? NULL) == 'admin' && $adminFieldVisible == TRUE) || - $className == 'CRM_Event_Form_ParticipantFeeSelection' - ) { - $fieldId = $field['id']; - $elementName = 'price_' . $fieldId; - - $isRequire = $field['is_required'] ?? NULL; - if ($button == 'skip') { - $isRequire = FALSE; - } - - //user might modified w/ hook. - $options = $field['options'] ?? NULL; - $formClasses = ['CRM_Event_Form_ParticipantFeeSelection']; - - if (!is_array($options)) { - continue; - } - elseif ($hideAdminValues && !in_array($className, $formClasses)) { - $publicVisibilityID = CRM_Price_BAO_PriceField::getVisibilityOptionID('public'); - $adminVisibilityID = CRM_Price_BAO_PriceField::getVisibilityOptionID('admin'); - - foreach ($options as $key => $currentOption) { - $optionVisibility = CRM_Utils_Array::value('visibility_id', $currentOption, $publicVisibilityID); - if ($optionVisibility == $adminVisibilityID) { - unset($options[$key]); - } - } - } - - $optionFullIds = CRM_Utils_Array::value('option_full_ids', $field, []); - - //soft suppress required rule when option is full. - if (!empty($optionFullIds) && (count($options) == count($optionFullIds))) { - $isRequire = FALSE; - } - if (!empty($options)) { - //build the element. - CRM_Price_BAO_PriceField::addQuickFormElement($form, - $elementName, - $fieldId, - FALSE, - $isRequire, - NULL, - $options, - $optionFullIds - ); - } - } - } - $form->_priceSet['id'] ??= $priceSetID; - $form->assign('priceSet', $form->_priceSet); - } - else { - // Is this reachable? - CRM_Core_Error::deprecatedWarning('code believed to be unreachable'); - $eventFeeBlockValues = $elements = $elementJS = []; - foreach ($form->_feeBlock as $fee) { - if (is_array($fee)) { - - //CRM-7632, CRM-6201 - $totalAmountJs = NULL; - $eventFeeBlockValues['amount_id_' . $fee['amount_id']] = $fee['value']; - $elements[$fee['amount_id']] = CRM_Utils_Money::format($fee['value']) . ' ' . $fee['label']; - $elementJS[$fee['amount_id']] = $totalAmountJs; - } - } - $form->assign('eventFeeBlockValues', json_encode($eventFeeBlockValues)); - - $form->_defaults['amount'] = $form->_values['event']['default_fee_id'] ?? NULL; - $element = &$form->addRadio('amount', ts('Event Fee(s)'), $elements, [], '
', FALSE, $elementJS); - if (isset($form->_online) && $form->_online) { - $element->freeze(); - } - if ($required) { - $form->addRule('amount', ts('Fee Level is a required field.'), 'required'); - } - } - } - - /** - * @param CRM_Event_Form_Registration $form - */ - public static function formatFieldsForOptionFull(&$form) { - $priceSet = $form->get('priceSet'); - $priceSetId = $form->get('priceSetId'); - $defaultPricefieldIds = []; - if (!empty($form->_values['line_items'])) { - foreach ($form->_values['line_items'] as $lineItem) { - $defaultPricefieldIds[] = $lineItem['price_field_value_id']; - } - } - if (!$priceSetId || - !is_array($priceSet) || - empty($priceSet) || empty($priceSet['optionsMaxValueTotal']) - ) { - return; - } - - $skipParticipants = $formattedPriceSetDefaults = []; - if (!empty($form->_allowConfirmation) && (isset($form->_pId) || isset($form->_additionalParticipantId))) { - $participantId = $form->_pId ?? $form->_additionalParticipantId; - $pricesetDefaults = CRM_Event_Form_EventFees::setDefaultPriceSet($participantId, - $form->_eventId - ); - // modify options full to respect the selected fields - // options on confirmation. - $formattedPriceSetDefaults = self::formatPriceSetParams($form, $pricesetDefaults); - - // to skip current registered participants fields option count on confirmation. - $skipParticipants[] = $form->_participantId; - if (!empty($form->_additionalParticipantIds)) { - $skipParticipants = array_merge($skipParticipants, $form->_additionalParticipantIds); - } - } - - $className = CRM_Utils_System::getClassName($form); - - //get the current price event price set options count. - $currentOptionsCount = self::getPriceSetOptionCount($form); - $recordedOptionsCount = CRM_Event_BAO_Participant::priceSetOptionsCount($form->_eventId, $skipParticipants); - $optionFullTotalAmount = 0; - $currentParticipantNo = (int) substr($form->_name, 12); - foreach ($form->_feeBlock as & $field) { - $optionFullIds = []; - $fieldId = $field['id']; - if (!is_array($field['options'])) { - continue; - } - foreach ($field['options'] as & $option) { - $optId = $option['id']; - $count = $option['count'] ?? 0; - $maxValue = $option['max_value'] ?? 0; - $dbTotalCount = $recordedOptionsCount[$optId] ?? 0; - $currentTotalCount = $currentOptionsCount[$optId] ?? 0; - - $totalCount = $currentTotalCount + $dbTotalCount; - $isFull = FALSE; - if ($maxValue && - (($totalCount >= $maxValue) && - (empty($form->_lineItem[$currentParticipantNo][$optId]['price_field_id']) || $dbTotalCount >= $maxValue)) - ) { - $isFull = TRUE; - $optionFullIds[$optId] = $optId; - if ($field['html_type'] != 'Select') { - if (in_array($optId, $defaultPricefieldIds)) { - $optionFullTotalAmount += $option['amount'] ?? 0; - } - } - else { - if (!empty($defaultPricefieldIds) && in_array($optId, $defaultPricefieldIds)) { - unset($optionFullIds[$optId]); - } - } - } - //here option is not full, - //but we don't want to allow participant to increase - //seats at the time of re-walking registration. - if ($count && - !empty($form->_allowConfirmation) && - !empty($formattedPriceSetDefaults) - ) { - if (empty($formattedPriceSetDefaults["price_{$field}"]) || empty($formattedPriceSetDefaults["price_{$fieldId}"][$optId])) { - $optionFullIds[$optId] = $optId; - $isFull = TRUE; - } - } - $option['is_full'] = $isFull; - $option['db_total_count'] = $dbTotalCount; - $option['total_option_count'] = $dbTotalCount + $currentTotalCount; - } - - //finally get option ids in. - $field['option_full_ids'] = $optionFullIds; - } - $form->assign('optionFullTotalAmount', $optionFullTotalAmount); - } - /** * Global form rule. * @@ -817,12 +568,15 @@ public static function formRule($fields, $files, $form) { if (!$form->_skipDupeRegistrationCheck) { self::checkRegistration($fields, $form); } + $spacesAvailable = $form->getEventValue('available_spaces'); //check for availability of registrations. - if (!$form->_allowConfirmation && empty($fields['bypass_payment']) && - is_numeric($form->_availableRegistrations) && - CRM_Utils_Array::value('additional_participants', $fields) >= $form->_availableRegistrations + if ($form->getEventValue('max_participants') !== NULL + && !$form->_allowConfirmation + && !empty($fields['additional_participants']) + && empty($fields['bypass_payment']) && + ((int) $fields['additional_participants']) >= $spacesAvailable ) { - $errors['additional_participants'] = ts("There is only enough space left on this event for %1 participant(s).", [1 => $form->_availableRegistrations]); + $errors['additional_participants'] = ts("There is only enough space left on this event for %1 participant(s).", [1 => $spacesAvailable]); } $numberAdditionalParticipants = $fields['additional_participants'] ?? 0; @@ -840,9 +594,13 @@ public static function formRule($fields, $files, $form) { } //don't allow to register w/ waiting if enough spaces available. - if (!empty($fields['bypass_payment']) && $form->_allowConfirmation) { - if (!is_numeric($form->_availableRegistrations) || - (empty($fields['priceSetId']) && CRM_Utils_Array::value('additional_participants', $fields) < $form->_availableRegistrations) + // @todo - this might not be working too well cos bypass_payment is working over time here. + // it will always be true if there is no payment on the form & it's a bit hard + // to determine if this is and we don't want people trying to confirm registration to be blocked here + /// see https://lab.civicrm.org/dev/core/-/issues/5168 + if ($form->getPriceSetID() && !empty($fields['bypass_payment']) && $form->_allowConfirmation) { + if ($spacesAvailable === 0 || + (empty($fields['priceSetId']) && CRM_Utils_Array::value('additional_participants', $fields) < $spacesAvailable) ) { $errors['bypass_payment'] = ts("You have not been added to the waiting list because there are spaces available for this event. We recommend registering yourself for an available space instead."); } @@ -855,8 +613,8 @@ public static function formRule($fields, $files, $form) { //format params. $formatted = self::formatPriceSetParams($form, $fields); $ppParams = [$formatted]; - $priceSetErrors = self::validatePriceSet($form, $ppParams); - $primaryParticipantCount = self::getParticipantCount($form, $ppParams); + $priceSetErrors = $form->validatePriceSet($ppParams, $fields['priceSetId'], $form->get('priceSet')); + $primaryParticipantCount = $form->getParticipantCount($ppParams); //get price set fields errors in. $errors = array_merge($errors, CRM_Utils_Array::value(0, $priceSetErrors, [])); @@ -866,12 +624,11 @@ public static function formRule($fields, $files, $form) { $totalParticipants += $numberAdditionalParticipants; } - if (empty($fields['bypass_payment']) && + if ($form->getEventValue('max_participants') !== NULL && empty($fields['bypass_payment']) && !$form->_allowConfirmation && - is_numeric($form->_availableRegistrations) && - $form->_availableRegistrations < $totalParticipants + $spacesAvailable < $totalParticipants ) { - $errors['_qf_default'] = ts("Only %1 Registrations available.", [1 => $form->_availableRegistrations]); + $errors['_qf_default'] = ts("Only %1 Registrations available.", [1 => $spacesAvailable]); } $lineItem = []; @@ -1002,7 +759,7 @@ public function postProcess() { } //hack to allow group to register w/ waiting - $primaryParticipantCount = self::getParticipantCount($this, $params); + $primaryParticipantCount = $this->getParticipantCount($params); $totalParticipants = $primaryParticipantCount; if (!empty($params['additional_participants'])) { @@ -1050,13 +807,11 @@ public function postProcess() { $params['amount'] = $this->_values['discount'][$discountId][$params['amount']]['value']; } elseif (empty($params['priceSetId'])) { - CRM_Core_Error::deprecatedWarning('unreachable code price set is always set here - passed as a hidden field although we could just load...'); + // We would wind up here if waitlisting - in which case there should be no amount set. if (!empty($params['amount'])) { + CRM_Core_Error::deprecatedWarning('unreachable code price set is always set here - passed as a hidden field although we could just load...'); $params['amount'] = $this->_values['fee'][$params['amount']]['value']; } - else { - $params['amount'] = ''; - } } else { $lineItem = []; @@ -1076,7 +831,7 @@ public function postProcess() { $this->set('lineItemParticipantsCount', [$primaryParticipantCount]); } - $this->set('amount', $params['amount']); + $this->set('amount', $params['amount'] ?? 0); $this->set('amount_level', $params['amount_level']); // generate and set an invoiceID for this transaction @@ -1253,4 +1008,28 @@ public static function checkRegistration($fields, $form, $isAdditional = FALSE) } } + /** + * Is it appropriate to suppress the payment elements on the form. + * + * We hide the price and payment fields if the event is full or requires approval, + * and the current user has not yet been approved CRM-12279 + * + * @return bool + * @throws \CRM_Core_Exception + */ + private function isSuppressPayment(): bool { + if (!$this->getPriceSetID()) { + return TRUE; + } + if ($this->_allowConfirmation) { + // They might be paying for a now-confirmed registration. + return FALSE; + } + if ($this->getSubmittedValue('bypass_payment')) { + // Value set by javascript on the form. + return TRUE; + } + return $this->isEventFull() || $this->_requireApproval; + } + } diff --git a/www/modules/civicrm/CRM/Event/Form/Registration/ThankYou.php b/www/modules/civicrm/CRM/Event/Form/Registration/ThankYou.php index eef8c1640..aed03f845 100644 --- a/www/modules/civicrm/CRM/Event/Form/Registration/ThankYou.php +++ b/www/modules/civicrm/CRM/Event/Form/Registration/ThankYou.php @@ -15,10 +15,10 @@ * @package CRM * @copyright CiviCRM LLC https://civicrm.org/licensing */ +use Civi\Api4\PCPBlock; /** * This class generates form components for processing Event - * */ class CRM_Event_Form_Registration_ThankYou extends CRM_Event_Form_Registration { @@ -26,19 +26,16 @@ class CRM_Event_Form_Registration_ThankYou extends CRM_Event_Form_Registration { * Set variables up before form is built. * * @return void + * @throws \CRM_Core_Exception */ - public function preProcess() { + public function preProcess(): void { parent::preProcess(); $this->_params = $this->get('params'); $this->_lineItem = $this->get('lineItem'); - $this->_part = $this->get('part'); - $this->_totalAmount = $this->get('totalAmount'); - $this->_receiveDate = $this->get('receiveDate'); - $this->_trxnId = $this->get('trxnId'); $finalAmount = $this->get('finalAmount'); $this->assign('finalAmount', $finalAmount); $participantInfo = $this->get('participantInfo'); - $this->assign('part', $this->_part); + $this->assign('part', $this->get('part')); $this->assign('participantInfo', $participantInfo); $customGroup = $this->get('customProfile'); $this->assign('customProfile', $customGroup); @@ -55,19 +52,19 @@ public function preProcess() { * * @return int */ - public function getAction() { + public function getAction(): int { if ($this->_action & CRM_Core_Action::PREVIEW) { return CRM_Core_Action::VIEW | CRM_Core_Action::PREVIEW; } - else { - return CRM_Core_Action::VIEW; - } + + return CRM_Core_Action::VIEW; } /** * Build the form object. * * @return void + * @throws \CRM_Core_Exception */ public function buildQuickForm() { // Assign the email address from a contact id lookup as in CRM_Event_BAO_Event->sendMail() @@ -91,7 +88,7 @@ public function buildQuickForm() { $lineItemForTemplate = []; if (!empty($this->_lineItem) && is_array($this->_lineItem)) { foreach ($this->_lineItem as $key => $value) { - if (!empty($value) && $value != 'skip') { + if (!empty($value) && $value !== 'skip') { $lineItemForTemplate[$key] = $value; if ($invoicing) { foreach ($value as $v) { @@ -114,18 +111,16 @@ public function buildQuickForm() { if ($invoicing) { $this->assign('totalTaxAmount', $taxAmount); } - $this->assign('totalAmount', $this->_totalAmount); + $this->assign('totalAmount', $this->get('totalAmount')); $hookDiscount = $this->get('hookDiscount'); if ($hookDiscount) { $this->assign('hookDiscount', $hookDiscount); } - $this->assign('receive_date', $this->_receiveDate); - $this->assign('trxn_id', $this->_trxnId); - - //cosider total amount. - $this->assign('isAmountzero', $this->_totalAmount <= 0); + $this->assign('receive_date', $this->get('receiveDate')); + $this->assign('trxn_id', $this->get('trxnId')); + $this->assign('isAmountzero', $this->get('totalAmount') <= 0); $this->assign('defaultRole', FALSE); if (($this->_params[0]['defaultRole'] ?? NULL) == 1) { @@ -142,7 +137,7 @@ public function buildQuickForm() { foreach ($fields as $name => $dontCare) { if (isset($this->_params[0][$name])) { $defaults[$name] = $this->_params[0][$name]; - if (substr($name, 0, 7) == 'custom_') { + if (str_starts_with($name, 'custom_')) { $timeField = "{$name}_time"; if (isset($this->_params[0][$timeField])) { $defaults[$timeField] = $this->_params[0][$timeField]; @@ -195,18 +190,8 @@ public function buildQuickForm() { } $this->assign('isOnWaitlist', $isOnWaitlist); $this->assign('isRequireApproval', $isRequireApproval); - - // find pcp info - $dao = new CRM_PCP_DAO_PCPBlock(); - $dao->entity_table = 'civicrm_event'; - $dao->entity_id = $this->_eventId; - $dao->is_active = 1; - $dao->find(TRUE); - - if ($dao->id) { - $this->assign('pcpLink', CRM_Utils_System::url('civicrm/contribute/campaign', 'action=add&reset=1&pageId=' . $this->_eventId . '&component=event')); - $this->assign('pcpLinkText', $dao->link_text); - } + $this->assign('pcpLink', $this->getPCPBlockID() ? CRM_Utils_System::url('civicrm/contribute/campaign', 'action=add&reset=1&pageId=' . $this->getEventID() . '&component=event') : NULL); + $this->assign('pcpLinkText', $this->getPCPBlockID() ? $this->getPCPBlockValue('link_text') : NULL); // Assign Participant Count to Lineitem Table $this->assign('pricesetFieldsCount', CRM_Price_BAO_PriceSet::getPricesetCount($this->_priceSetId)); @@ -221,7 +206,7 @@ public function buildQuickForm() { * * @return void */ - public function postProcess() { + public function postProcess(): void { } /** @@ -229,8 +214,43 @@ public function postProcess() { * * @return string */ - public function getTitle() { + public function getTitle(): string { return ts('Thank You Page'); } + /** + * @return int|null + * @throws \CRM_Core_Exception + */ + public function getPCPBlockID(): ?int { + if (!$this->isDefined('PCPBlock')) { + $pcpBlock = PCPBlock::get(FALSE) + ->addWhere('entity_table', '=', 'civicrm_event') + ->addWhere('entity_id', '=', $this->getEventID()) + ->addWhere('is_active', '=', TRUE) + ->execute()->first(); + if (!$pcpBlock) { + return NULL; + } + $this->define('PCPBlock', 'PCPBlock', $pcpBlock); + } + return $this->lookup('PCPBlock', 'id'); + } + + /** + * Get a PCP Block value. + * + * @param string $value + * + * @return mixed|null + * @throws \CRM_Core_Exception + * @todo - this should probably be on a trait & made public like similar getValue functions. + */ + protected function getPCPBlockValue(string $value) { + if (!$this->getPCPBlockID()) { + return NULL; + } + return $this->lookup('PCPBlock', $value); + } + } diff --git a/www/modules/civicrm/CRM/Event/Form/Task/AddToGroup.php b/www/modules/civicrm/CRM/Event/Form/Task/AddToGroup.php index f64f44202..114b53ff4 100644 --- a/www/modules/civicrm/CRM/Event/Form/Task/AddToGroup.php +++ b/www/modules/civicrm/CRM/Event/Form/Task/AddToGroup.php @@ -115,7 +115,7 @@ public function buildQuickForm() { // also set the group title $groupValues = ['id' => $this->_id, 'title' => $this->_title]; - $this->assign_by_ref('group', $groupValues); + $this->assign('group', $groupValues); } // Set dynamic page title for 'Add Members Group (confirm)' diff --git a/www/modules/civicrm/CRM/Event/Import/Controller.php b/www/modules/civicrm/CRM/Event/Import/Controller.php deleted file mode 100644 index 8d80c0758..000000000 --- a/www/modules/civicrm/CRM/Event/Import/Controller.php +++ /dev/null @@ -1,41 +0,0 @@ -_stateMachine = new CRM_Import_StateMachine($this, $action); - - // create and instantiate the pages - $this->addPages($this->_stateMachine, $action); - - // add all the actions - $config = CRM_Core_Config::singleton(); - $this->addActions($config->uploadDir, ['uploadFile']); - } - -} diff --git a/www/modules/civicrm/CRM/Event/Import/Form/MapField.php b/www/modules/civicrm/CRM/Event/Import/Form/MapField.php index ecdb46445..df06bae5e 100644 --- a/www/modules/civicrm/CRM/Event/Import/Form/MapField.php +++ b/www/modules/civicrm/CRM/Event/Import/Form/MapField.php @@ -50,11 +50,6 @@ public function preProcess() { unset($this->_mapperFields[$value]); } } - elseif ( - $this->getSubmittedValue('onDuplicate') == CRM_Import_Parser::DUPLICATE_SKIP - || $this->getSubmittedValue('onDuplicate') == CRM_Import_Parser::DUPLICATE_NOCHECK) { - unset($this->_mapperFields['participant_id']); - } } /** @@ -82,9 +77,7 @@ public function buildQuickForm() { * list of errors to be posted back to the form */ public static function formRule($fields, $files, $self) { - $errors = []; - // define so we avoid notices below - $errors['_qf_default'] = ''; + $requiredError = []; if (!array_key_exists('savedMapping', $fields)) { $importKeys = []; @@ -98,24 +91,26 @@ public static function formRule($fields, $files, $self) { ]; $contactFieldsBelowWeightMessage = self::validateRequiredContactMatchFields($self->getContactType(), $importKeys); - + if (in_array('id', $importKeys)) { + // ID is the only field we need, if present. + $requiredFields = []; + } foreach ($requiredFields as $field => $title) { if (!in_array($field, $importKeys)) { if ($field === 'contact_id') { - if (!$contactFieldsBelowWeightMessage || in_array('external_identifier', $importKeys) || - in_array('participant_id', $importKeys) + if (!$contactFieldsBelowWeightMessage || in_array('external_identifier', $importKeys) ) { continue; } if ($self->isUpdateExisting()) { - $errors['_qf_default'] .= ts('Missing required field: Provide Participant ID') . '
'; + $requiredError[] = ts('Missing required field: Provide Participant ID') . '
'; } else { - $errors['_qf_default'] .= ts('Missing required contact matching fields.') . " $contactFieldsBelowWeightMessage " . ' ' . ts('Or Provide Contact ID or External ID.') . '
'; + $requiredError[] = ts('Missing required contact matching fields.') . " $contactFieldsBelowWeightMessage " . ' ' . ts('Or Provide Contact ID or External ID.') . '
'; } } elseif (!in_array('event_title', $importKeys)) { - $errors['_qf_default'] .= ts('Missing required field: Provide %1 or %2', + $requiredError[] = ts('Missing required field: Provide %1 or %2', [1 => $title, 2 => 'Event Title'] ) . '
'; } @@ -123,11 +118,7 @@ public static function formRule($fields, $files, $self) { } } - if (empty($errors['_qf_default'])) { - unset($errors['_qf_default']); - } - - return empty($errors) ? TRUE : $errors; + return empty($requiredError) ? TRUE : ['_qf_default' => implode('_mapperKeys = &$mapperKeys; - } /** * Get information about the provided job. @@ -92,10 +59,10 @@ public function import(array $values): void { $rowNumber = (int) ($values[array_key_last($values)]); try { $params = $this->getMappedRow($values); - if ($params['external_identifier']) { + if (!empty($params['external_identifier'])) { $params['contact_id'] = $this->lookupExternalIdentifier($params['external_identifier'], $this->getContactType(), $params['contact_id'] ?? NULL); } - $session = CRM_Core_Session::singleton(); + $formatted = $params; // don't add to recent items, CRM-4399 $formatted['skipRecentView'] = TRUE; @@ -109,41 +76,16 @@ public function import(array $values): void { $formatValues[$key] = $field; } - $formatError = $this->formatValues($formatted, $formatValues); - - if ($formatError) { - throw new CRM_Core_Exception($formatError['error_message']); - } - - if ($this->isUpdateExisting()) { - if (!empty($formatValues['participant_id'])) { - $dao = new CRM_Event_BAO_Participant(); - $dao->id = $formatValues['participant_id']; - - if ($dao->find(TRUE)) { - $ids = [ - 'participant' => $formatValues['participant_id'], - 'userId' => $session->get('userID'), - ]; - $participantValues = []; - //@todo calling api functions directly is not supported - $newParticipant = $this->deprecated_participant_check_params($formatted, $participantValues, FALSE); - if ($newParticipant['error_message']) { - throw new CRM_Core_Exception($newParticipant['error_message']); - } - $newParticipant = CRM_Event_BAO_Participant::create($formatted, $ids); - if (!empty($formatted['fee_level'])) { - $otherParams = [ - 'fee_label' => $formatted['fee_level'], - 'event_id' => $newParticipant->event_id, - ]; - CRM_Price_BAO_LineItem::syncLineItems($newParticipant->id, 'civicrm_participant', $newParticipant->fee_amount, $otherParams); - } - $this->setImportStatus($rowNumber, 'IMPORTED', '', $newParticipant->id); - return; - } - throw new CRM_Core_Exception('Matching Participant record not found for Participant ID ' . $formatValues['participant_id'] . '. Row was skipped.'); + if (!empty($params['id'])) { + $this->checkEntityExists('Participant', $params['id']); + if (!$this->isUpdateExisting()) { + throw new CRM_Core_Exception(ts('% record found and update not selected', [1 => 'Participant'])); } + //@todo calling api functions directly is not supported + $this->deprecated_participant_check_params($formatted); + $newParticipant = CRM_Event_BAO_Participant::create($formatted); + $this->setImportStatus($rowNumber, 'IMPORTED', '', $newParticipant->id); + return; } if (empty($params['contact_id'])) { @@ -223,107 +165,6 @@ public function import(array $values): void { $this->setImportStatus($rowNumber, 'IMPORTED', '', $newParticipant['id']); } - /** - * Format values - * - * @todo lots of tidy up needed here - very old function relocated. - * - * @param array $values - * @param array $params - * - * @return array|null - */ - protected function formatValues(&$values, $params) { - $fields = CRM_Event_DAO_Participant::fields(); - _civicrm_api3_store_values($fields, $params, $values); - - $customFields = CRM_Core_BAO_CustomField::getFields('Participant', FALSE, FALSE, NULL, NULL, FALSE, FALSE, FALSE); - - foreach ($params as $key => $value) { - // ignore empty values or empty arrays etc - if (CRM_Utils_System::isNull($value)) { - continue; - } - - switch ($key) { - case 'participant_contact_id': - if (!CRM_Utils_Rule::integer($value)) { - return civicrm_api3_create_error("contact_id not valid: $value"); - } - if (!CRM_Core_DAO::singleValueQuery("SELECT id FROM civicrm_contact WHERE id = $value")) { - return civicrm_api3_create_error("Invalid Contact ID: There is no contact record with contact_id = $value."); - } - $values['contact_id'] = $values['participant_contact_id']; - unset($values['participant_contact_id']); - break; - - case 'participant_register_date': - if (!CRM_Utils_Rule::dateTime($value)) { - return civicrm_api3_create_error("$key not a valid date: $value"); - } - break; - - case 'participant_status_id': - if (!CRM_Utils_Rule::integer($value)) { - return civicrm_api3_create_error("Event Status ID is not valid: $value"); - } - break; - - case 'participant_status': - $status = CRM_Event_PseudoConstant::participantStatus(); - $values['participant_status_id'] = CRM_Utils_Array::key($value, $status); - break; - - case 'participant_role_id': - case 'participant_role': - $role = CRM_Event_PseudoConstant::participantRole(); - $participantRoles = explode(",", $value); - foreach ($participantRoles as $k => $v) { - $v = trim($v); - if ($key == 'participant_role') { - $participantRoles[$k] = CRM_Utils_Array::key($v, $role); - } - else { - $participantRoles[$k] = $v; - } - } - $values['role_id'] = implode(CRM_Core_DAO::VALUE_SEPARATOR, $participantRoles); - unset($values[$key]); - break; - - default: - break; - } - } - - if (array_key_exists('participant_note', $params)) { - $values['participant_note'] = $params['participant_note']; - } - - // CRM_Event_BAO_Participant::create() handles register_date, - // status_id and source. So, if $values contains - // participant_register_date, participant_status_id or participant_source, - // convert it to register_date, status_id or source - $changes = [ - 'participant_register_date' => 'register_date', - 'participant_source' => 'source', - 'participant_status_id' => 'status_id', - 'participant_role_id' => 'role_id', - 'participant_fee_level' => 'fee_level', - 'participant_fee_amount' => 'fee_amount', - 'participant_id' => 'id', - ]; - - foreach ($changes as $orgVal => $changeVal) { - if (isset($values[$orgVal])) { - $values[$changeVal] = $values[$orgVal]; - unset($values[$orgVal]); - } - } - - return NULL; - } - /** * @param array $params * @@ -356,21 +197,12 @@ protected function deprecated_create_participant_formatted($params) { */ protected function deprecated_participant_check_params($params, $checkDuplicate = FALSE) { - // check if participant id is valid or not - if (!empty($params['id'])) { - $participant = new CRM_Event_BAO_Participant(); - $participant->id = $params['id']; - if (!$participant->find(TRUE)) { - return civicrm_api3_create_error(ts('Participant id is not valid')); - } - } - // check if contact id is valid or not if (!empty($params['contact_id'])) { $contact = new CRM_Contact_BAO_Contact(); $contact->id = $params['contact_id']; if (!$contact->find(TRUE)) { - return civicrm_api3_create_error(ts('Contact id is not valid')); + throw new CRM_Core_Exception(ts('Contact id is not valid')); } } @@ -378,7 +210,7 @@ protected function deprecated_participant_check_params($params, $checkDuplicate if (!empty($params['event_id'])) { $isTemplate = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $params['event_id'], 'is_template'); if (!empty($isTemplate)) { - return civicrm_api3_create_error(ts('Event templates are not meant to be registered.')); + throw new CRM_Core_Exception(ts('Event templates are not meant to be registered.')); } } @@ -400,13 +232,13 @@ protected function deprecated_participant_check_params($params, $checkDuplicate ); } } - return TRUE; } /** * Set up field metadata. * * @return void + * @throws \CRM_Core_Exception */ protected function setFieldMetadata(): void { if (empty($this->importableFieldsMetadata)) { @@ -421,22 +253,13 @@ protected function setFieldMetadata(): void { 'options' => FALSE, ], ], - CRM_Event_DAO_Participant::import(), + $this->getImportFieldsForEntity('Participant'), CRM_Core_BAO_CustomField::getFieldsForImport('Participant'), $this->getContactMatchingFields() ); - $fields['participant_contact_id']['title'] .= ' (match to contact)'; - $fields['participant_contact_id']['html']['label'] = $fields['participant_contact_id']['title']; - foreach ($fields as $index => $field) { - if (isset($field['name']) && $field['name'] !== $index) { - // undo unique names - participant is the primary - // entity and no others have conflicting unique names - // if we ever added them the should have unique names - v4api style - $fields[$field['name']] = $field; - unset($fields[$index]); - } - } + $fields['contact_id']['title'] .= ' (match to contact)'; + $fields['contact_id']['html']['label'] = $fields['contact_id']['title']; $this->importableFieldsMetadata = $fields; } } diff --git a/www/modules/civicrm/CRM/Event/Info.php b/www/modules/civicrm/CRM/Event/Info.php index a468dc516..1298f162b 100644 --- a/www/modules/civicrm/CRM/Event/Info.php +++ b/www/modules/civicrm/CRM/Event/Info.php @@ -41,53 +41,41 @@ public function getInfo() { /** * @inheritDoc - * @param bool $getAllUnconditionally - * @param bool $descriptions - * Whether to return permission descriptions - * - * @return array */ - public function getPermissions($getAllUnconditionally = FALSE, $descriptions = FALSE) { + public function getPermissions(): array { $permissions = [ 'access CiviEvent' => [ - ts('access CiviEvent'), - ts('Create events, view all events, and view participant records (for visible contacts)'), + 'label' => ts('access CiviEvent'), + 'description' => ts('Create events, view all events, and view participant records (for visible contacts)'), ], 'edit event participants' => [ - ts('edit event participants'), - ts('Record and update backend event registrations'), + 'label' => ts('edit event participants'), + 'description' => ts('Record and update backend event registrations'), ], 'edit all events' => [ - ts('edit all events'), - ts('Edit events even without specific ACL granted'), + 'label' => ts('edit all events'), + 'description' => ts('Edit events even without specific ACL granted'), ], 'register for events' => [ - ts('register for events'), - ts('Register for events online'), + 'label' => ts('register for events'), + 'description' => ts('Register for events online'), ], 'view event info' => [ - ts('view event info'), - ts('View online event information pages'), + 'label' => ts('view event info'), + 'description' => ts('View online event information pages'), ], 'view event participants' => [ - ts('view event participants'), + 'label' => ts('view event participants'), ], 'delete in CiviEvent' => [ - ts('delete in CiviEvent'), - ts('Delete participants and events that you can edit'), + 'label' => ts('delete in CiviEvent'), + 'description' => ts('Delete participants and events that you can edit'), ], 'manage event profiles' => [ - ts('manage event profiles'), - ts('Allow users to create, edit and copy event-related profile forms used for online event registration.'), + 'label' => ts('manage event profiles'), + 'description' => ts('Allow users to create, edit and copy event-related profile forms used for online event registration.'), ], ]; - - if (!$descriptions) { - foreach ($permissions as $name => $attr) { - $permissions[$name] = array_shift($attr); - } - } - return $permissions; } diff --git a/www/modules/civicrm/CRM/Event/Page/EventInfo.php b/www/modules/civicrm/CRM/Event/Page/EventInfo.php index 8f9d9d2e4..6073eee19 100644 --- a/www/modules/civicrm/CRM/Event/Page/EventInfo.php +++ b/www/modules/civicrm/CRM/Event/Page/EventInfo.php @@ -20,6 +20,8 @@ */ class CRM_Event_Page_EventInfo extends CRM_Core_Page { + use CRM_Event_Form_EventFormTrait; + /** * Run the page. * @@ -72,22 +74,14 @@ public function run() { // Add Event Type to $values in case folks want to display it $values['event']['event_type'] = CRM_Utils_Array::value($values['event']['event_type_id'], CRM_Event_PseudoConstant::eventType()); - $this->assign('isShowLocation', CRM_Utils_Array::value('is_show_location', $values['event'])); + $this->assign('isShowLocation', $values['event']['is_show_location'] ?? NULL); $eventCurrency = CRM_Utils_Array::value('currency', $values['event'], $config->defaultCurrency); $this->assign('eventCurrency', $eventCurrency); // show event fees. if ($this->_id && !empty($values['event']['is_monetary'])) { - - //CRM-10434 - $discountId = CRM_Core_BAO_Discount::findSet($this->_id, 'civicrm_event'); - if ($discountId) { - $priceSetId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_Discount', $discountId, 'price_set_id'); - } - else { - $priceSetId = CRM_Price_BAO_PriceSet::getFor('civicrm_event', $this->_id); - } + $priceSetId = $this->getPriceSetID(); // get price set options, - CRM-5209 if ($priceSetId) { @@ -207,8 +201,8 @@ public function run() { 'lat' => (float ) ($maxLat - $minLat), 'lng' => (float ) ($maxLng - $minLng), ]; - $this->assign_by_ref('center', $center); - $this->assign_by_ref('span', $span); + $this->assign('center', $center); + $this->assign('span', $span); if ($action == CRM_Core_Action::PREVIEW) { $mapURL = CRM_Utils_System::url('civicrm/contact/map/event', "eid={$this->_id}&reset=1&action=preview", @@ -246,35 +240,32 @@ public function run() { } $hasWaitingList = $values['event']['has_waitlist'] ?? NULL; - $eventFullMessage = CRM_Event_BAO_Participant::eventFull($this->_id, - FALSE, - $hasWaitingList - ); + $isEventOpenForRegistration = CRM_Event_BAO_Event::validRegistrationRequest($values['event'], $this->_id); $allowRegistration = FALSE; - $isEventOpenForRegistration = CRM_Event_BAO_Event::validRegistrationRequest($values['event'], $this->_id); - if (!empty($values['event']['is_online_registration'])) { - if ($isEventOpenForRegistration == 1) { - // we always generate urls for the front end in joomla - $action_query = $action === CRM_Core_Action::PREVIEW ? "&action=$action" : ''; - $url = CRM_Utils_System::url('civicrm/event/register', - "id={$this->_id}&reset=1{$action_query}", - FALSE, NULL, TRUE, - TRUE - ); - if (!$eventFullMessage || $hasWaitingList) { - $registerText = ts('Register Now'); - if (!empty($values['event']['registration_link_text'])) { - $registerText = $values['event']['registration_link_text']; - } + // check that the user has permission to register for this event + // Looks like the form shows either way - just not the register option. + $hasPermission = CRM_Core_Permission::event(CRM_Core_Permission::EDIT, + $this->getEventID(), 'register for events' + ); + if ($hasPermission && $this->isAvailableForOnlineRegistration()) { + // we always generate urls for the front end in joomla + $action_query = $action === CRM_Core_Action::PREVIEW ? "&action=$action" : ''; + $url = CRM_Utils_System::url('civicrm/event/register', + "id={$this->_id}&reset=1{$action_query}", + FALSE, NULL, TRUE, + TRUE + ); + $registerText = ts('Register Now'); + if (!empty($values['event']['registration_link_text'])) { + $registerText = $values['event']['registration_link_text']; + } - //Fixed for CRM-4855 - $allowRegistration = CRM_Event_BAO_Event::showHideRegistrationLink($values); + //Fixed for CRM-4855 + $allowRegistration = CRM_Event_BAO_Event::showHideRegistrationLink($values); - $this->assign('registerText', $registerText); - $this->assign('registerURL', $url); - } - } + $this->assign('registerText', $registerText); + $this->assign('registerURL', $url); } $this->assign('registerClosed', !empty($values['event']['is_online_registration']) && !$isEventOpenForRegistration && CRM_Core_Permission::check('register for events')); @@ -287,8 +278,8 @@ public function run() { 'role_id' => $values['event']['default_role_id'] ?? NULL, ]; - if ($eventFullMessage && ($noFullMsg == 'false') || CRM_Event_BAO_Event::checkRegistration($params)) { - $statusMessage = $eventFullMessage; + if (($this->isEventFull() && $noFullMsg === 'false') || CRM_Event_BAO_Event::checkRegistration($params)) { + $statusMessage = $this->getEventValue('event_full_text'); if (CRM_Event_BAO_Event::checkRegistration($params)) { if ($noFullMsg == 'false') { if ($values['event']['allow_same_participant_emails']) { @@ -361,4 +352,29 @@ public function getTemplateFileName() { return parent::getTemplateFileName(); } + /** + * Get the price set ID for the event. + * + * @return int|null + * + * @api This function will not change in a minor release and is supported for + * use outside of core. This annotation / external support for properties + * is only given where there is specific test cover. + * + * @noinspection PhpUnhandledExceptionInspection + * @noinspection PhpDocMissingThrowsInspection + */ + public function getPriceSetID(): ?int { + if ($this->getEventValue('is_monetary')) { + //CRM-10434 + $discountID = CRM_Core_BAO_Discount::findSet($this->getEventID(), 'civicrm_event'); + if ($discountID) { + return (int) CRM_Core_DAO::getFieldValue('CRM_Core_DAO_Discount', $discountID, 'price_set_id'); + } + + return (int) CRM_Price_BAO_PriceSet::getFor('civicrm_event', $this->getEventID()); + } + return NULL; + } + } diff --git a/www/modules/civicrm/CRM/Event/Page/ManageEvent.php b/www/modules/civicrm/CRM/Event/Page/ManageEvent.php index aaf51395c..c3d7efdad 100644 --- a/www/modules/civicrm/CRM/Event/Page/ManageEvent.php +++ b/www/modules/civicrm/CRM/Event/Page/ManageEvent.php @@ -588,7 +588,7 @@ public function pager($whereClause, $whereParams) { $params['total'] = CRM_Core_DAO::singleValueQuery($query, $whereParams); $this->_pager = new CRM_Utils_Pager($params); - $this->assign_by_ref('pager', $this->_pager); + $this->assign('pager', $this->_pager); } /** diff --git a/www/modules/civicrm/CRM/Event/Page/ParticipantListing/NameStatusAndDate.php b/www/modules/civicrm/CRM/Event/Page/ParticipantListing/NameStatusAndDate.php index 2d53edfb3..e3adcda30 100644 --- a/www/modules/civicrm/CRM/Event/Page/ParticipantListing/NameStatusAndDate.php +++ b/www/modules/civicrm/CRM/Event/Page/ParticipantListing/NameStatusAndDate.php @@ -94,7 +94,7 @@ public function run() { ]; $rows[] = $row; } - $this->assign_by_ref('rows', $rows); + $this->assign('rows', $rows); return parent::run(); } @@ -124,7 +124,7 @@ public function pager($fromClause, $whereClause, $whereParams) { $params['total'] = CRM_Core_DAO::singleValueQuery($query, $whereParams); $this->_pager = new CRM_Utils_Pager($params); - $this->assign_by_ref('pager', $this->_pager); + $this->assign('pager', $this->_pager); } /** @@ -157,8 +157,8 @@ public function orderBy() { ); } $sort = new CRM_Utils_Sort($headers, $sortID); - $this->assign_by_ref('headers', $headers); - $this->assign_by_ref('sort', $sort); + $this->assign('headers', $headers); + $this->assign('sort', $sort); $this->set(CRM_Utils_Sort::SORT_ID, $sort->getCurrentSortID() ); diff --git a/www/modules/civicrm/CRM/Event/Page/ParticipantListing/Simple.php b/www/modules/civicrm/CRM/Event/Page/ParticipantListing/Simple.php index 10d1f69e5..0b3f6812d 100644 --- a/www/modules/civicrm/CRM/Event/Page/ParticipantListing/Simple.php +++ b/www/modules/civicrm/CRM/Event/Page/ParticipantListing/Simple.php @@ -87,7 +87,7 @@ public function run() { ]; $rows[] = $row; } - $this->assign_by_ref('rows', $rows); + $this->assign('rows', $rows); return parent::run(); } @@ -118,7 +118,7 @@ public function pager($fromClause, $whereClause, $whereParams) { $params['total'] = CRM_Core_DAO::singleValueQuery($query, $whereParams); $this->_pager = new CRM_Utils_Pager($params); - $this->assign_by_ref('pager', $this->_pager); + $this->assign('pager', $this->_pager); } /** @@ -148,8 +148,8 @@ public function orderBy() { ); } $sort = new CRM_Utils_Sort($headers, $sortID); - $this->assign_by_ref('headers', $headers); - $this->assign_by_ref('sort', $sort); + $this->assign('headers', $headers); + $this->assign('sort', $sort); $this->set(CRM_Utils_Sort::SORT_ID, $sort->getCurrentSortID() ); diff --git a/www/modules/civicrm/CRM/Event/Page/Tab.php b/www/modules/civicrm/CRM/Event/Page/Tab.php index 295953a30..ba4734e4a 100644 --- a/www/modules/civicrm/CRM/Event/Page/Tab.php +++ b/www/modules/civicrm/CRM/Event/Page/Tab.php @@ -39,13 +39,6 @@ public function browse() { $displayName = CRM_Contact_BAO_Contact::displayName($this->_contactId); $this->assign('displayName', $displayName); $this->ajaxResponse['tabCount'] = CRM_Contact_BAO_Contact::getCountComponent('participant', $this->_contactId); - // Refresh other tabs with related data - $this->ajaxResponse['updateTabs'] = [ - '#tab_activity' => CRM_Contact_BAO_Contact::getCountComponent('activity', $this->_contactId), - ]; - if (CRM_Core_Permission::access('CiviContribute')) { - $this->ajaxResponse['updateTabs']['#tab_contribute'] = CRM_Contact_BAO_Contact::getCountComponent('contribution', $this->_contactId); - } } } diff --git a/www/modules/civicrm/CRM/Event/Selector/Search.php b/www/modules/civicrm/CRM/Event/Selector/Search.php index 0b7c92736..ae83870d6 100644 --- a/www/modules/civicrm/CRM/Event/Selector/Search.php +++ b/www/modules/civicrm/CRM/Event/Selector/Search.php @@ -329,7 +329,7 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { // Skip registration if event_id is NULL if (empty($row['event_id'])) { - Civi::log()->warning('Participant record without event ID. You have invalid data in your database!'); + Civi::log()->warning('Participant record (' . $row['participant_id'] . ') without event ID. You have invalid data in your database!'); continue; } @@ -402,7 +402,7 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $result->participant_id ); - $row['contact_type'] = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ? $result->contact_sub_type : $result->contact_type, FALSE, $result->contact_id + $row['contact_type'] = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ?: $result->contact_type, FALSE, $result->contact_id ); $row['paid'] = CRM_Event_BAO_Event::isMonetary($row['event_id']); @@ -425,7 +425,7 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { } $rows[] = $row; } - CRM_Core_Selector_Controller::$_template->assign_by_ref('lineItems', $lineItems); + CRM_Core_Smarty::singleton()->assign('lineItems', $lineItems ?? []); return $rows; } diff --git a/www/modules/civicrm/CRM/Event/WorkflowMessage/ParticipantConfirm.php b/www/modules/civicrm/CRM/Event/WorkflowMessage/ParticipantConfirm.php new file mode 100644 index 000000000..3b5f28c72 --- /dev/null +++ b/www/modules/civicrm/CRM/Event/WorkflowMessage/ParticipantConfirm.php @@ -0,0 +1,26 @@ +830
- civicrm/event/import - Import Participants - CRM_Event_Import_Controller - access CiviEvent,edit event participants - 1 - 840 + civicrm/import/participant + Import Participants + CRM_Import_Controller + access CiviEvent,edit event participants + 1 + 840 civicrm/event/price diff --git a/www/modules/civicrm/CRM/Export/BAO/Export.php b/www/modules/civicrm/CRM/Export/BAO/Export.php index 3442efe22..4fcd7a733 100644 --- a/www/modules/civicrm/CRM/Export/BAO/Export.php +++ b/www/modules/civicrm/CRM/Export/BAO/Export.php @@ -146,7 +146,7 @@ public static function exportComponents( $paymentDetails = []; if ($processor->isExportPaymentFields()) { // get payment related in for event and members - $paymentDetails = CRM_Contribute_BAO_Contribution::getContributionDetails($exportMode, $ids); + $paymentDetails = $processor->getContributionDetails(); //get all payment headers. // If we haven't selected specific payment fields, load in all the // payment headers. diff --git a/www/modules/civicrm/CRM/Export/BAO/ExportProcessor.php b/www/modules/civicrm/CRM/Export/BAO/ExportProcessor.php index 2b26fd102..1e935d9ce 100644 --- a/www/modules/civicrm/CRM/Export/BAO/ExportProcessor.php +++ b/www/modules/civicrm/CRM/Export/BAO/ExportProcessor.php @@ -394,7 +394,7 @@ public function setAddresseeGreetingTemplate($addresseeGreetingTemplate) { * - addressee_other */ public function __construct($exportMode, $requestedFields, $queryOperator, $isMergeSameHousehold = FALSE, $isPostalableOnly = FALSE, $isMergeSameAddress = FALSE, $formValues = []) { - $this->setExportMode($exportMode); + $this->setExportMode((int) $exportMode); $this->setQueryMode(); $this->setQueryOperator($queryOperator); $this->setRequestedFields($requestedFields); @@ -697,14 +697,14 @@ public function setQueryMode() { /** * @return int */ - public function getExportMode() { + public function getExportMode(): int { return $this->exportMode; } /** * @param int $exportMode */ - public function setExportMode($exportMode) { + public function setExportMode(int $exportMode) { $this->exportMode = $exportMode; } @@ -2367,4 +2367,65 @@ protected function getContactPortionOfGreeting(int $contactID, int $greetingID, return $copyPostalGreeting; } + /** + * Get the contribution details for component export. + * + * @internal do not call from outside core. + * + * @return array + * associated array + */ + public function getContributionDetails() { + $paymentDetails = []; + $componentClause = ' IN ( ' . implode(',', $this->ids) . ' ) '; + + if ($this->getExportMode() === CRM_Export_Form_Select::EVENT_EXPORT) { + $componentSelect = " civicrm_participant_payment.participant_id id"; + $additionalClause = " +INNER JOIN civicrm_participant_payment ON (civicrm_contribution.id = civicrm_participant_payment.contribution_id +AND civicrm_participant_payment.participant_id {$componentClause} ) +"; + } + elseif ($this->getExportMode() === CRM_Export_Form_Select::MEMBER_EXPORT) { + $componentSelect = " civicrm_membership_payment.membership_id id"; + $additionalClause = " +INNER JOIN civicrm_membership_payment ON (civicrm_contribution.id = civicrm_membership_payment.contribution_id +AND civicrm_membership_payment.membership_id {$componentClause} ) +"; + } + elseif ($this->getExportMode() === CRM_Export_Form_Select::PLEDGE_EXPORT) { + $componentSelect = " civicrm_pledge_payment.id id"; + $additionalClause = " +INNER JOIN civicrm_pledge_payment ON (civicrm_contribution.id = civicrm_pledge_payment.contribution_id +AND civicrm_pledge_payment.pledge_id {$componentClause} ) +"; + } + + $query = " SELECT total_amount, contribution_status.name as status_id, contribution_status.label as status, payment_instrument.name as payment_instrument, receive_date, + trxn_id, {$componentSelect} +FROM civicrm_contribution +LEFT JOIN civicrm_option_group option_group_payment_instrument ON ( option_group_payment_instrument.name = 'payment_instrument') +LEFT JOIN civicrm_option_value payment_instrument ON (civicrm_contribution.payment_instrument_id = payment_instrument.value + AND option_group_payment_instrument.id = payment_instrument.option_group_id ) +LEFT JOIN civicrm_option_group option_group_contribution_status ON (option_group_contribution_status.name = 'contribution_status') +LEFT JOIN civicrm_option_value contribution_status ON (civicrm_contribution.contribution_status_id = contribution_status.value + AND option_group_contribution_status.id = contribution_status.option_group_id ) +{$additionalClause} +"; + + $dao = CRM_Core_DAO::executeQuery($query); + + while ($dao->fetch()) { + $paymentDetails[$dao->id] = [ + 'total_amount' => $dao->total_amount, + 'contribution_status' => $dao->status, + 'receive_date' => $dao->receive_date, + 'pay_instru' => $dao->payment_instrument, + 'trxn_id' => $dao->trxn_id, + ]; + } + + return $paymentDetails; + } + } diff --git a/www/modules/civicrm/CRM/Export/Controller/Standalone.php b/www/modules/civicrm/CRM/Export/Controller/Standalone.php index 4e38d4a9d..8a0504a2b 100644 --- a/www/modules/civicrm/CRM/Export/Controller/Standalone.php +++ b/www/modules/civicrm/CRM/Export/Controller/Standalone.php @@ -49,7 +49,7 @@ public function __construct($title = NULL, $action = CRM_Core_Action::NONE, $mod // add all the actions $this->addActions(); - $dao = CRM_Core_DAO_AllCoreTables::getFullName($entity); + $dao = CRM_Core_DAO_AllCoreTables::getDAONameForEntity($entity); CRM_Utils_System::setTitle(ts('Export %1', [1 => $dao::getEntityTitle(TRUE)])); } diff --git a/www/modules/civicrm/CRM/Export/Form/Select.php b/www/modules/civicrm/CRM/Export/Form/Select.php index 42601a11e..20c830104 100644 --- a/www/modules/civicrm/CRM/Export/Form/Select.php +++ b/www/modules/civicrm/CRM/Export/Form/Select.php @@ -74,7 +74,7 @@ public function preProcess() { $this->_componentClause = NULL; // FIXME: This should use a modified version of CRM_Contact_Form_Search::getModeValue but it doesn't have all the contexts - // FIXME: Or better still, use CRM_Core_DAO_AllCoreTables::getBriefName($daoName) to get the $entityShortName + // FIXME: Or better still, use CRM_Core_DAO_AllCoreTables::getEntityNameForClass($daoName) to get the $entityShortName $entityShortname = $this->getEntityShortName(); if (!in_array($entityShortname, ['Contact', 'Contribute', 'Member', 'Event', 'Pledge', 'Case', 'Grant', 'Activity'], TRUE)) { diff --git a/www/modules/civicrm/CRM/Extension/Manager.php b/www/modules/civicrm/CRM/Extension/Manager.php index 921983dd1..178f6e858 100644 --- a/www/modules/civicrm/CRM/Extension/Manager.php +++ b/www/modules/civicrm/CRM/Extension/Manager.php @@ -524,6 +524,17 @@ public function getStatus($key) { } } + /** + * Check if a given extension is installed and enabled + * + * @param $key + * + * @return bool + */ + public function isEnabled($key) { + return ($this->getStatus($key) === self::STATUS_INSTALLED); + } + /** * Check if a given extension is incompatible with this version of CiviCRM * diff --git a/www/modules/civicrm/CRM/Extension/Mapper.php b/www/modules/civicrm/CRM/Extension/Mapper.php index 62fdd94ed..29664d857 100644 --- a/www/modules/civicrm/CRM/Extension/Mapper.php +++ b/www/modules/civicrm/CRM/Extension/Mapper.php @@ -553,7 +553,7 @@ public function refresh() { * @return string */ public function getUpgradeLink($remoteExtensionInfo, $localExtensionInfo) { - if (!empty($remoteExtensionInfo) && version_compare($localExtensionInfo['version'], $remoteExtensionInfo->version, '<')) { + if (!empty($remoteExtensionInfo) && version_compare($localExtensionInfo['version'] ?? '', $remoteExtensionInfo->version, '<')) { return ts('Version %1 is installed. Upgrade to version %3.', [ 1 => $localExtensionInfo['version'], 2 => 'href="' . CRM_Utils_System::url('civicrm/admin/extensions', "action=update&id={$localExtensionInfo['key']}&key={$localExtensionInfo['key']}") . '"', diff --git a/www/modules/civicrm/CRM/Financial/BAO/FinancialItem.php b/www/modules/civicrm/CRM/Financial/BAO/FinancialItem.php index ef7fd5b35..d46e6be7f 100644 --- a/www/modules/civicrm/CRM/Financial/BAO/FinancialItem.php +++ b/www/modules/civicrm/CRM/Financial/BAO/FinancialItem.php @@ -9,6 +9,9 @@ +--------------------------------------------------------------------+ */ +use Civi\Api4\FinancialAccount; +use Civi\Api4\FinancialItem; + /** * * @package CRM @@ -260,16 +263,19 @@ public static function checkContactPresent($contactIds, &$error) { * @return array */ public static function getPreviousFinancialItem($entityId) { - $params = [ - 'entity_id' => $entityId, - 'entity_table' => 'civicrm_line_item', - 'options' => ['limit' => 1, 'sort' => 'id DESC'], - ]; - $salesTaxFinancialAccounts = civicrm_api3('FinancialAccount', 'get', ['is_tax' => 1]); - if ($salesTaxFinancialAccounts['count']) { - $params['financial_account_id'] = ['NOT IN' => array_keys($salesTaxFinancialAccounts['values'])]; + $financialItemAPI = FinancialItem::get(FALSE) + ->addWhere('entity_id', '=', $entityId) + ->addWhere('entity_table', '=', 'civicrm_line_item') + ->addOrderBy('id', 'DESC'); + + $salesTaxFinancialAccounts = FinancialAccount::get(FALSE) + ->addSelect('id') + ->addWhere('is_tax', '=', 1) + ->execute(); + if ($salesTaxFinancialAccounts->count() > 0) { + $financialItemAPI->addWhere('financial_account_id', 'NOT IN', $salesTaxFinancialAccounts->column('id')); } - return civicrm_api3('FinancialItem', 'getsingle', $params); + return $financialItemAPI->execute()->first(); } /** diff --git a/www/modules/civicrm/CRM/Financial/BAO/FinancialType.php b/www/modules/civicrm/CRM/Financial/BAO/FinancialType.php index 1e1f35cf4..167c2f76d 100644 --- a/www/modules/civicrm/CRM/Financial/BAO/FinancialType.php +++ b/www/modules/civicrm/CRM/Financial/BAO/FinancialType.php @@ -19,18 +19,6 @@ */ class CRM_Financial_BAO_FinancialType extends CRM_Financial_DAO_FinancialType implements \Civi\Core\HookInterface { - /** - * Static cache holder of available financial types for this session - * @var array - */ - public static $_availableFinancialTypes = []; - - /** - * Static cache holder of status of ACL-FT enabled/disabled for this session - * @var array - */ - public static $_statusACLFt = []; - /** * @deprecated * @param array $params diff --git a/www/modules/civicrm/CRM/Financial/BAO/Order.php b/www/modules/civicrm/CRM/Financial/BAO/Order.php index 49763a905..89c3994b1 100644 --- a/www/modules/civicrm/CRM/Financial/BAO/Order.php +++ b/www/modules/civicrm/CRM/Financial/BAO/Order.php @@ -675,12 +675,15 @@ protected function setPriceFieldMetadata(array $metadata): void { $metadata[$index]['supports_auto_renew'] = FALSE; if ($this->isExcludeExpiredFields && !$priceField['is_active']) { unset($metadata[$index]); + continue; } if ($this->isExcludeExpiredFields && !empty($priceField['active_on']) && time() < strtotime($priceField['active_on'])) { unset($metadata[$index]); + continue; } elseif ($this->isExcludeExpiredFields && !empty($priceField['expire_on']) && strtotime($priceField['expire_on']) < time()) { unset($metadata[$index]); + continue; } elseif (!empty($priceField['options'])) { foreach ($priceField['options'] as $optionID => $option) { @@ -821,6 +824,27 @@ public function getLineItems():array { return $this->lineItems; } + /** + * Is participant count being used. + * + * This would be true if at least one price field value + * has a count value that is not 0 or NULL. In this case + * the row value will be used for the participant. + * + * @return bool + * @throws \CRM_Core_Exception + */ + public function isUseParticipantCount(): bool { + foreach ($this->getPriceFieldsMetadata() as $fieldMetadata) { + foreach ($fieldMetadata['options'] as $option) { + if (($option['count'] ?? 0) > 0) { + return TRUE; + } + } + } + return FALSE; + } + /** * Recalculate the line items. * @@ -977,11 +1001,12 @@ protected function calculateLineItems(): array { elseif ($this->getPriceFieldMetadata($lineItem['price_field_id'])['name'] === 'other_amount') { // Other amount is a front end user entered form. It is reasonable to think it would be tax inclusive. $lineItem['line_total_inclusive'] = $lineItem['line_total']; - $lineItem['line_total'] = $lineItem['line_total_inclusive'] / (1 + ($lineItem['tax_rate'] / 100)); + $lineItem['line_total'] = $lineItem['line_total_inclusive'] ? $lineItem['line_total_inclusive'] / (1 + ($lineItem['tax_rate'] / 100)) : 0; $lineItem['tax_amount'] = round($lineItem['line_total_inclusive'] - $lineItem['line_total'], 2); // Make sure they still add up to each other afer the rounding. $lineItem['line_total'] = $lineItem['line_total_inclusive'] - $lineItem['tax_amount']; - $lineItem['unit_price'] = $lineItem['line_total'] / $lineItem['qty']; + $lineItem['qty'] = 1; + $lineItem['unit_price'] = $lineItem['line_total']; } elseif ($taxRate) { diff --git a/www/modules/civicrm/CRM/Financial/BAO/Payment.php b/www/modules/civicrm/CRM/Financial/BAO/Payment.php index d5ab9090e..eb075cc23 100644 --- a/www/modules/civicrm/CRM/Financial/BAO/Payment.php +++ b/www/modules/civicrm/CRM/Financial/BAO/Payment.php @@ -15,8 +15,10 @@ * @copyright CiviCRM LLC https://civicrm.org/licensing */ +use Civi\Api4\Contribution; use Civi\Api4\FinancialItem; use Civi\Api4\LineItem; +use Civi\Api4\EntityFinancialTrxn; /** * This class contains payment related functions. @@ -40,10 +42,13 @@ class CRM_Financial_BAO_Payment { * @throws \CRM_Core_Exception */ public static function create(array $params): CRM_Financial_DAO_FinancialTrxn { - $contribution = civicrm_api3('Contribution', 'getsingle', ['id' => $params['contribution_id']]); - $contributionStatus = CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $contribution['contribution_status_id']); + $contribution = Contribution::get(FALSE) + ->addWhere('id', '=', $params['contribution_id']) + ->addSelect('*', 'contribution_status_id:name', 'balance_amount', 'paid_amount') + ->execute()->first(); + $contributionStatus = $contribution['contribution_status_id:name']; $isPaymentCompletesContribution = self::isPaymentCompletesContribution($params['contribution_id'], $params['total_amount'], $contributionStatus); - $lineItems = self::getPayableLineItems($params); + $payableItems = self::getPayableItems($params, $contribution); $whiteList = ['check_number', 'payment_processor_id', 'fee_amount', 'total_amount', 'contribution_id', 'net_amount', 'card_type_id', 'pan_truncation', 'trxn_result_code', 'payment_instrument_id', 'trxn_id', 'trxn_date', 'order_reference']; $paymentTrxnParams = array_intersect_key($params, array_fill_keys($whiteList, 1)); @@ -100,91 +105,33 @@ public static function create(array $params): CRM_Financial_DAO_FinancialTrxn { self::reverseAllocationsFromPreviousPayment($params, $trxn->id); } else { - // Record new "payment" (financial_trxn, financial_item, entity_financial_trxn etc). - $salesTaxFinancialAccount = CRM_Contribute_BAO_Contribution::getSalesTaxFinancialAccounts(); - // Get all the lineitems and add financial_item information to them for the contribution on which we are recording a payment. - $financialItems = LineItem::get(FALSE) - ->addSelect('tax_amount', 'price_field_value_id', 'financial_item.id', 'financial_item.status_id:name', 'financial_item.financial_account_id', 'financial_item.entity_id') - ->addJoin( - 'FinancialItem AS financial_item', - 'INNER', - NULL, - ['financial_item.entity_table', '=', '"civicrm_line_item"'], - ['financial_item.entity_id', '=', 'id'] - ) - ->addOrderBy('financial_item.id', 'DESC') - ->addWhere('contribution_id', '=', (int) $params['contribution_id'])->execute(); - - // Loop through our list of payable lineitems - foreach ($lineItems as $lineItem) { - if ($lineItem['allocation'] === (float) 0) { + // Link the payment with the relevant financial items, by creating EntityFinancialItems. + // We also ensure the status of the Item is set to Paid or Partially Paid as appropriate. + foreach ($payableItems as $payableItem) { + if ($payableItem['allocation'] === 0.0) { continue; } - $financialItemID = NULL; - $currentFinancialItemStatus = NULL; - foreach ($financialItems as $financialItem) { - // $financialItems is a list of all lineitems for the contribution - // Loop through all of them and match on the first one which is not of type "Sales Tax". - if ($financialItem['financial_item.entity_id'] === (int) $lineItem['id'] - && !in_array($financialItem['financial_item.financial_account_id'], $salesTaxFinancialAccount, TRUE) - ) { - $financialItemID = $financialItem['financial_item.id']; - $currentFinancialItemStatus = $financialItem['financial_item.status_id:name']; - // We can break out of the loop because there will only be one lineitem=financial_item.entity_id. - break; - } - } - if (!$financialItemID) { - // If we didn't find a lineitem that is NOT of type "Sales Tax" then create a new one. - $financialItemID = self::getNewFinancialItemID($lineItem, $params['trxn_date'], $contribution['contact_id'], $paymentTrxnParams['currency']); - } - // Now create an EntityFinancialTrxn record to link the new financial_trxn to the lineitem and mark it as paid. - $eftParams = [ + EntityFinancialTrxn::create(FALSE)->setValues([ 'entity_table' => 'civicrm_financial_item', 'financial_trxn_id' => $trxn->id, - 'entity_id' => $financialItemID, - 'amount' => $lineItem['allocation'], - ]; - civicrm_api3('EntityFinancialTrxn', 'create', $eftParams); + 'entity_id' => $payableItem['financial_item.id'], + 'amount' => $payableItem['allocation'], + ])->execute(); - if ($currentFinancialItemStatus && ('Paid' !== $currentFinancialItemStatus)) { - // Did the lineitem get fully paid? - $newStatus = $lineItem['allocation'] < $lineItem['balance'] ? 'Partially paid' : 'Paid'; + if ('Paid' !== $payableItem['financial_item.status_id:name']) { + // Did the item get fully paid? + $newStatus = $payableItem['allocation'] < $payableItem['balance'] ? 'Partially paid' : 'Paid'; FinancialItem::update(FALSE) ->addValue('status_id:name', $newStatus) - ->addWhere('id', '=', $financialItemID) + ->addWhere('id', '=', $payableItem['financial_item.id']) ->execute(); } - - foreach ($financialItems as $financialItem) { - // $financialItems is a list of all lineitems for the contribution - // Now we loop through all of them and match on the first one which IS of type "Sales Tax". - if ($financialItem['financial_item.entity_id'] === (int) $lineItem['id'] - && in_array($financialItem['financial_item.financial_account_id'], $salesTaxFinancialAccount, TRUE) - ) { - // If we find a "Sales Tax" lineitem we record a tax entry in entityFiancncialTrxn - // @todo - this is expected to be broken - it should be fixed to - // a) have the getPayableLineItems add the amount to allocate for tax - // b) call EntityFinancialTrxn directly - per above. - // - see https://github.com/civicrm/civicrm-core/pull/14763 - $entityParams = [ - 'contribution_total_amount' => $contribution['total_amount'], - 'trxn_total_amount' => $params['total_amount'], - 'trxn_id' => $trxn->id, - 'line_item_amount' => $financialItem['tax_amount'], - ]; - $eftParams['entity_id'] = $financialItem['financial_item.id']; - CRM_Contribute_BAO_Contribution::createProportionalEntry($entityParams, $eftParams); - } - } } } self::updateRelatedContribution($params, $params['contribution_id']); if ($isPaymentCompletesContribution) { if ($contributionStatus === 'Pending refund') { - // Ideally we could still call completetransaction as non-payment related actions should - // be outside this class. However, for now we just update the contribution here. // Unit test cover in CRM_Event_BAO_AdditionalPaymentTest::testTransactionInfo. civicrm_api3('Contribution', 'create', [ @@ -218,9 +165,11 @@ public static function create(array $params): CRM_Financial_DAO_FinancialTrxn { civicrm_api3('Participant', 'create', ['id' => $participantPayment['participant_id'], 'status_id' => 'Partially paid']); } } + // Note that we reload the payments rather than use $contribution['paid_amount'] + // here as we are interested in the paid_amount AFTER this payment has been made. elseif ($contributionStatus === 'Completed' && ((float) CRM_Core_BAO_FinancialTrxn::getTotalPayments($contribution['id'], TRUE) === 0.0)) { // If the contribution has previously been completed (fully paid) and now has total payments adding up to 0 - // change status to refunded. + // change status to 'refunded'. self::updateContributionStatus($contribution['id'], 'Refunded'); } CRM_Contribute_BAO_Contribution::recordPaymentActivity($params['contribution_id'], $params['participant_id'] ?? NULL, $params['total_amount'], $trxn->currency, $trxn->trxn_date); @@ -528,7 +477,10 @@ private static function updateContributionStatus(int $contributionID, string $st /** * Get the line items for the contribution. * - * Retrieve the line items and wrangle the following + * Retrieve the financial items that need to be linked to the payment. + * + * EntityFinancialItems will be added to the sum of the Payment total + * linking it to these items. * * - get the outstanding balance on a line item basis. * - determine what amount is being paid on this line item - we get the total being paid @@ -536,80 +488,86 @@ private static function updateContributionStatus(int $contributionID, string $st * and then assign apply that ratio to each line item. * - if overrides have been passed in we use those amounts instead. * - * @param $params + * @param array $params + * @param array $contribution * * @return array * @throws \CRM_Core_Exception */ - protected static function getPayableLineItems($params): array { - $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($params['contribution_id']); - $lineItemOverrides = []; - if (!empty($params['line_item'])) { - // The format is a bit weird here - $params['line_item'] => [[1 => 10], [2 => 40]] - // Squash to [1 => 10, 2 => 40] - foreach ($params['line_item'] as $lineItem) { - $lineItemOverrides += $lineItem; - } - } - $outstandingBalance = CRM_Contribute_BAO_Contribution::getContributionBalance($params['contribution_id']); + protected static function getPayableItems(array $params, array $contribution): array { + $outstandingBalance = $contribution['balance_amount']; if ($outstandingBalance !== 0.0) { $ratio = $params['total_amount'] / $outstandingBalance; } elseif ($params['total_amount'] < 0) { - $ratio = $params['total_amount'] / (float) CRM_Core_BAO_FinancialTrxn::getTotalPayments($params['contribution_id'], TRUE); + $ratio = $params['total_amount'] / $contribution['paid_amount']; } else { // Help we are making a payment but no money is owed. We won't allocate the overpayment to any line item. $ratio = 0; } - foreach ($lineItems as $lineItemID => $lineItem) { - // Ideally id would be set deeper but for now just add in here. - $lineItems[$lineItemID]['id'] = $lineItemID; - $lineItems[$lineItemID]['paid'] = self::getAmountOfLineItemPaid($lineItemID); - $lineItems[$lineItemID]['balance'] = $lineItem['subTotal'] - $lineItems[$lineItemID]['paid']; + $lineItemOverrides = []; + if (!empty($params['line_item'])) { + // The format is a bit weird here - $params['line_item'] => [[1 => 10], [2 => 40]] + // Squash to [1 => 10, 2 => 40] + foreach ($params['line_item'] as $lineItem) { + $lineItemOverrides += $lineItem; + } + } + + $items = LineItem::get(FALSE) + ->addSelect('*', 'financial_item.status_id:name', 'financial_item.id', 'financial_item.financial_account_id', 'financial_item_id.currency', 'financial_item.financial_account_id.is_tax', 'financial_item.entity_id', 'financial_item.amount', 'allocated.amount') + ->addJoin( + 'FinancialItem AS financial_item', + 'LEFT', + ['financial_item.entity_table', '=', '"civicrm_line_item"'], + ['financial_item.entity_id', '=', 'id'] + ) + ->addJoin('EntityFinancialTrxn AS allocated', + 'LEFT', + ['allocated.entity_id', '=', 'financial_item.id'], + ['allocated.entity_table', '=', '"civicrm_financial_item"'], + ['allocated.financial_trxn_id.is_payment', '=', TRUE] + ) + // Ideally we would group by financial_item.id & get the sum of + // amount, but we hit full group by issues. + ->addOrderBy('financial_item.id') + ->addWhere('contribution_id', '=', (int) $params['contribution_id']) + ->execute(); + + $payableItems = []; + + foreach ($items as $item) { + $lineItemID = $item['id']; + if (!$item['financial_item.id']) { + // If we didn't find a financial item that is NOT of type "Sales Tax" then create a new one. + // This covers a situation that would not normally exist where the site has a data issue. + $item = self::createFinancialItem($item, $params['trxn_date'], $contribution['contact_id'], $contribution['currency']); + } + // Add up the amount paid by line item, separated into tax & non-tax. + // Up to 2 items per line item are added to payable items (tax + no tax). + // The item added from the last row 'wins' - it will have the totals based on the total + // of the amount paid across all of the rows. + // @todo perhaps this should be done by financial item, not line item. + $payableItemIndex = $item['financial_item.financial_account_id.is_tax'] ? ($item['id'] . '-tax') : $item['id']; + $item['paid'] = ($item['allocated.amount'] ?: 0) + ($payableItems[$payableItemIndex]['paid'] ?? 0); + $item['item_total'] = $item['financial_item.financial_account_id.is_tax'] ? $item['tax_amount'] : $item['line_total']; + $item['balance'] = $item['item_total'] - $item['paid']; if (!empty($lineItemOverrides)) { - $lineItems[$lineItemID]['allocation'] = $lineItemOverrides[$lineItemID] ?? NULL; + $item['allocation'] = $lineItemOverrides[$lineItemID] ?? NULL; } else { - if (empty($lineItems[$lineItemID]['balance']) && !empty($ratio) && $params['total_amount'] < 0) { - $lineItems[$lineItemID]['allocation'] = $lineItem['subTotal'] * $ratio; + if (empty($item['balance']) && !empty($ratio) && $params['total_amount'] < 0) { + $item['allocation'] = $item['item_total'] * $ratio; } else { - $lineItems[$lineItemID]['allocation'] = $lineItems[$lineItemID]['balance'] * $ratio; + $item['allocation'] = $item['balance'] * $ratio; } } + $payableItems[$payableItemIndex] = $item; } - return $lineItems; - } - /** - * Get the amount paid so far against this line item. - * - * @param int $lineItemID - * - * @return float - * - * @throws \CRM_Core_Exception - */ - protected static function getAmountOfLineItemPaid($lineItemID) { - $paid = 0; - $financialItems = civicrm_api3('FinancialItem', 'get', [ - 'entity_id' => $lineItemID, - 'entity_table' => 'civicrm_line_item', - 'options' => ['sort' => 'id DESC', 'limit' => 0], - ])['values']; - if (!empty($financialItems)) { - $entityFinancialTrxns = civicrm_api3('EntityFinancialTrxn', 'get', [ - 'entity_table' => 'civicrm_financial_item', - 'entity_id' => ['IN' => array_keys($financialItems)], - 'options' => ['limit' => 0], - 'financial_trxn_id.is_payment' => 1, - ])['values']; - foreach ($entityFinancialTrxns as $entityFinancialTrxn) { - $paid += $entityFinancialTrxn['amount']; - } - } - return (float) $paid; + return $payableItems; } /** @@ -661,27 +619,47 @@ protected static function reverseAllocationsFromPreviousPayment($params, $trxnID * @param int $contactID * @param string $currency * - * @return int + * @return array * * @throws \CRM_Core_Exception */ - protected static function getNewFinancialItemID($lineItem, $trxn_date, $contactID, $currency): int { + protected static function createFinancialItem(array $lineItem, string $trxn_date, int $contactID, string $currency): array { $financialAccount = CRM_Financial_BAO_FinancialAccount::getFinancialAccountForFinancialTypeByRelationship( $lineItem['financial_type_id'], 'Income Account Is' ); - $itemParams = [ - 'transaction_date' => $trxn_date, - 'contact_id' => $contactID, - 'currency' => $currency, - 'amount' => $lineItem['line_total'], - 'description' => $lineItem['label'], - 'status_id' => 'Unpaid', - 'financial_account_id' => $financialAccount, - 'entity_table' => 'civicrm_line_item', - 'entity_id' => $lineItem['id'], - ]; - return (int) civicrm_api3('FinancialItem', 'create', $itemParams)['id']; + + FinancialItem::create(FALSE) + ->setValues([ + 'transaction_date' => $trxn_date, + 'contact_id' => $contactID, + 'currency' => $currency, + 'amount' => $lineItem['line_total'], + 'description' => $lineItem['label'], + 'status_id:name' => 'Unpaid', + 'financial_account_id' => $financialAccount, + 'entity_table' => 'civicrm_line_item', + 'entity_id' => $lineItem['id'], + ]) + ->execute(); + + return LineItem::get(FALSE) + ->addSelect('*', 'financial_item.status_id:name', 'financial_item.id', 'financial_item.financial_account_id', 'financial_item_id.currency', 'financial_item.financial_account_id.is_tax', 'financial_item.entity_id', 'financial_item.amount', 'allocated.amount') + ->addJoin( + 'FinancialItem AS financial_item', + 'LEFT', + ['financial_item.entity_table', '=', '"civicrm_line_item"'], + ['financial_item.entity_id', '=', 'id'] + ) + ->addJoin('EntityFinancialTrxn AS allocated', + 'LEFT', + ['allocated.entity_id', '=', 'financial_item.id'], + ['allocated.entity_table', '=', '"civicrm_financial_item"'], + ['allocated.financial_trxn_id.is_payment', '=', TRUE] + ) + ->addOrderBy('financial_item.id', 'DESC') + ->addWhere('id', '=', (int) $lineItem['id']) + ->execute()->first(); } } diff --git a/www/modules/civicrm/CRM/Financial/DAO/Currency.php b/www/modules/civicrm/CRM/Financial/DAO/Currency.php index 0d41a2eb6..f43ee794d 100644 --- a/www/modules/civicrm/CRM/Financial/DAO/Currency.php +++ b/www/modules/civicrm/CRM/Financial/DAO/Currency.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Financial/Currency.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:89ee3480af0f7086aa26de7937953b86) + * (GenCodeChecksum:f5d1798a453da2d166f143f5579b58cb) */ /** diff --git a/www/modules/civicrm/CRM/Financial/DAO/EntityFinancialAccount.php b/www/modules/civicrm/CRM/Financial/DAO/EntityFinancialAccount.php index 30a7a459f..c8d1400f3 100644 --- a/www/modules/civicrm/CRM/Financial/DAO/EntityFinancialAccount.php +++ b/www/modules/civicrm/CRM/Financial/DAO/EntityFinancialAccount.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Financial/EntityFinancialAccount.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:dc2ac86086d6245161241084f0bfdd45) + * (GenCodeChecksum:e70f42c50c6b264f47371a93183e7010) */ /** diff --git a/www/modules/civicrm/CRM/Financial/DAO/EntityFinancialTrxn.php b/www/modules/civicrm/CRM/Financial/DAO/EntityFinancialTrxn.php index 06862ba84..50fee5235 100644 --- a/www/modules/civicrm/CRM/Financial/DAO/EntityFinancialTrxn.php +++ b/www/modules/civicrm/CRM/Financial/DAO/EntityFinancialTrxn.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Financial/EntityFinancialTrxn.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:9e34bfe525333d841625c6f0c43cc62e) + * (GenCodeChecksum:4f50b4dc4237d1713da5f0e2616dc88c) */ /** diff --git a/www/modules/civicrm/CRM/Financial/DAO/FinancialAccount.php b/www/modules/civicrm/CRM/Financial/DAO/FinancialAccount.php index 77259c4b9..5f230c362 100644 --- a/www/modules/civicrm/CRM/Financial/DAO/FinancialAccount.php +++ b/www/modules/civicrm/CRM/Financial/DAO/FinancialAccount.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Financial/FinancialAccount.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:c1a56dd20c19e96296f4f70a4a690893) + * (GenCodeChecksum:b395b6212a005ebce453ce9938d77c7b) */ /** diff --git a/www/modules/civicrm/CRM/Financial/DAO/FinancialItem.php b/www/modules/civicrm/CRM/Financial/DAO/FinancialItem.php index 1b37347fc..02930a285 100644 --- a/www/modules/civicrm/CRM/Financial/DAO/FinancialItem.php +++ b/www/modules/civicrm/CRM/Financial/DAO/FinancialItem.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Financial/FinancialItem.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:a8a10fc175856157ba11e9cdc5420e0f) + * (GenCodeChecksum:89bf1fec762e66b5b561b4b2683a10c2) */ /** diff --git a/www/modules/civicrm/CRM/Financial/DAO/FinancialTrxn.php b/www/modules/civicrm/CRM/Financial/DAO/FinancialTrxn.php index e104eb35c..63fedf78e 100644 --- a/www/modules/civicrm/CRM/Financial/DAO/FinancialTrxn.php +++ b/www/modules/civicrm/CRM/Financial/DAO/FinancialTrxn.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Financial/FinancialTrxn.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:56b888e49da4fb4fd30e68316b16f8a7) + * (GenCodeChecksum:1c6e2bcd4812e16fcf3634adea81602d) */ /** diff --git a/www/modules/civicrm/CRM/Financial/DAO/FinancialType.php b/www/modules/civicrm/CRM/Financial/DAO/FinancialType.php index 5b4eb62bf..962107e25 100644 --- a/www/modules/civicrm/CRM/Financial/DAO/FinancialType.php +++ b/www/modules/civicrm/CRM/Financial/DAO/FinancialType.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Financial/FinancialType.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:f34814d42721174a2f55bc744f497b6f) + * (GenCodeChecksum:e501792be30fab09a33951030b21232e) */ /** diff --git a/www/modules/civicrm/CRM/Financial/DAO/PaymentProcessor.php b/www/modules/civicrm/CRM/Financial/DAO/PaymentProcessor.php index bc3917435..9016b9dae 100644 --- a/www/modules/civicrm/CRM/Financial/DAO/PaymentProcessor.php +++ b/www/modules/civicrm/CRM/Financial/DAO/PaymentProcessor.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Financial/PaymentProcessor.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:4f2a2d7198a66ebeb31aa0f8ae8640f7) + * (GenCodeChecksum:404aed364d3a1c0a8969748f0771c755) */ /** diff --git a/www/modules/civicrm/CRM/Financial/DAO/PaymentProcessorType.php b/www/modules/civicrm/CRM/Financial/DAO/PaymentProcessorType.php index 64bae4436..fb5e4b1e1 100644 --- a/www/modules/civicrm/CRM/Financial/DAO/PaymentProcessorType.php +++ b/www/modules/civicrm/CRM/Financial/DAO/PaymentProcessorType.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Financial/PaymentProcessorType.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:c0f54fc4f1f69c631c0c2e89537c6194) + * (GenCodeChecksum:f283d3921734802806597d973fc07345) */ /** diff --git a/www/modules/civicrm/CRM/Financial/DAO/PaymentToken.php b/www/modules/civicrm/CRM/Financial/DAO/PaymentToken.php index 46e956849..2a7cd1917 100644 --- a/www/modules/civicrm/CRM/Financial/DAO/PaymentToken.php +++ b/www/modules/civicrm/CRM/Financial/DAO/PaymentToken.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Financial/PaymentToken.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:e2af4137d7b9b510bbc239e5b829e5ba) + * (GenCodeChecksum:57629c7015cfc98ac29b5380a25636cb) */ /** diff --git a/www/modules/civicrm/CRM/Financial/Form/FinancialAccount.php b/www/modules/civicrm/CRM/Financial/Form/FinancialAccount.php index c0c2b03a1..9af95e9c4 100644 --- a/www/modules/civicrm/CRM/Financial/Form/FinancialAccount.php +++ b/www/modules/civicrm/CRM/Financial/Form/FinancialAccount.php @@ -20,6 +20,7 @@ */ class CRM_Financial_Form_FinancialAccount extends CRM_Contribute_Form { use CRM_Core_Form_EntityFormTrait; + use CRM_Custom_Form_CustomDataTrait; /** * Flag if its a AR account type. @@ -43,10 +44,6 @@ public function getDefaultEntity() { public function preProcess() { parent::preProcess(); - // Add custom data to form - CRM_Custom_Form_CustomData::preProcess($this, NULL, NULL, 1, 'FinancialAccount', $this->_id); - CRM_Custom_Form_CustomData::buildQuickForm($this); - if ($this->_id) { $params = [ 'id' => $this->_id, @@ -54,7 +51,7 @@ public function preProcess() { $financialAccount = CRM_Financial_BAO_FinancialAccount::retrieve($params); $financialAccountTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Asset' ")); if ($financialAccount->financial_account_type_id == $financialAccountTypeId - && strtolower($financialAccount->account_type_code) == 'ar' + && strtolower($financialAccount->account_type_code) === 'ar' && !CRM_Financial_BAO_FinancialAccount::getARAccounts($this->_id, $financialAccountTypeId) ) { $this->_isARFlag = TRUE; @@ -64,6 +61,8 @@ public function preProcess() { } } } + // Assigned for the ajax call to get custom data. + $this->assign('entityID', $this->_id); } /** @@ -75,6 +74,9 @@ public function buildQuickForm() { if ($this->_action & CRM_Core_Action::DELETE) { return; } + if ($this->isSubmitted()) { + $this->addCustomDataFieldsToForm('FinancialAccount'); + } $this->applyFilter('__ALL__', 'trim'); $attributes = CRM_Core_DAO::getAttribute('CRM_Financial_DAO_FinancialAccount'); @@ -114,7 +116,6 @@ public function buildQuickForm() { } } - $this->addCustomDataToForm(); if ($this->_action == CRM_Core_Action::UPDATE && CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_FinancialAccount', $this->_id, 'is_reserved') ) { @@ -173,7 +174,6 @@ public static function formRule($values, $files, $self) { */ public function setDefaultValues() { $defaults = parent::setDefaultValues(); - $defaults = array_merge($defaults, CRM_Custom_Form_CustomData::setDefaultValues($this)); if ($this->_action & CRM_Core_Action::ADD) { $defaults['contact_id'] = CRM_Core_BAO_Domain::getDomain()->contact_id; } @@ -195,7 +195,7 @@ public function postProcess() { else { // store the submitted values in an array $params = $this->exportValues(); - $params['custom'] = CRM_Core_BAO_CustomField::postProcess($this->_submitValues, $this->_id, 'FinancialAccount'); + $params['custom'] = CRM_Core_BAO_CustomField::postProcess($this->getSubmittedValues(), $this->_id, 'FinancialAccount'); if ($this->_action & CRM_Core_Action::UPDATE) { $params['id'] = $this->_id; diff --git a/www/modules/civicrm/CRM/Financial/Form/Payment.php b/www/modules/civicrm/CRM/Financial/Form/Payment.php index f52d2cad7..a5cffbacd 100644 --- a/www/modules/civicrm/CRM/Financial/Form/Payment.php +++ b/www/modules/civicrm/CRM/Financial/Form/Payment.php @@ -38,22 +38,29 @@ class CRM_Financial_Form_Payment extends CRM_Core_Form { */ public $_formName = ''; + /** + * @var int|null + */ + public ?int $paymentInstrumentID; + /** * Set variables up before form is built. + * + * @throws \Exception */ - public function preProcess() { + public function preProcess(): void { parent::preProcess(); $this->_formName = CRM_Utils_Request::retrieve('formName', 'String', $this); $this->_values['custom_pre_id'] = CRM_Utils_Request::retrieve('pre_profile_id', 'Integer', $this); - + // These properties are set because it is how CRM_Core_Payment_ProcessorForm::preProcess + // accesses them. Passing them in as properties might be more transparent. $this->_paymentProcessorID = CRM_Utils_Request::retrieve('processor_id', 'Integer', CRM_Core_DAO::$_nullObject, TRUE); $this->currency = CRM_Utils_Request::retrieve('currency', 'String', CRM_Core_DAO::$_nullObject, TRUE); - - $this->paymentInstrumentID = CRM_Utils_Request::retrieve('payment_instrument_id', 'Integer'); + $this->paymentInstrumentID = CRM_Utils_Request::retrieve('payment_instrument_id', 'Integer') ? (int) CRM_Utils_Request::retrieve('payment_instrument_id', 'Integer') : NULL; $this->isBackOffice = CRM_Utils_Request::retrieve('is_back_office', 'Integer'); $this->assignBillingType(); @@ -85,7 +92,7 @@ public function buildQuickForm() { /** * Set default values for the form. */ - public function setDefaultValues() { + public function setDefaultValues(): array { $contactID = $this->getContactID(); CRM_Core_Payment_Form::setDefaultValues($this, $contactID); return $this->_defaults; @@ -97,7 +104,7 @@ public function setDefaultValues() { * @param int $paymentProcessorID * @param string $region */ - public static function addCreditCardJs($paymentProcessorID = NULL, $region = 'billing-block') { + public static function addCreditCardJs($paymentProcessorID = NULL, $region = 'billing-block'): void { $creditCards = CRM_Financial_BAO_PaymentProcessor::getCreditCards($paymentProcessorID); if (empty($creditCards)) { $creditCards = CRM_Contribute_PseudoConstant::creditCard(); diff --git a/www/modules/civicrm/CRM/Financial/Form/PaymentEdit.php b/www/modules/civicrm/CRM/Financial/Form/PaymentEdit.php index 751b830bf..56c722e17 100644 --- a/www/modules/civicrm/CRM/Financial/Form/PaymentEdit.php +++ b/www/modules/civicrm/CRM/Financial/Form/PaymentEdit.php @@ -168,7 +168,7 @@ public function postProcess(): void { 'id' => $this->_id, 'payment_instrument_id' => $this->_submitValues['payment_instrument_id'], 'trxn_id' => $this->_submitValues['trxn_id'] ?? NULL, - 'trxn_date' => CRM_Utils_Array::value('trxn_date', $this->_submitValues, date('YmdHis')), + 'trxn_date' => $this->_submitValues['trxn_date'] ?? date('YmdHis'), ]; $paymentInstrumentName = CRM_Core_PseudoConstant::getName('CRM_Financial_DAO_FinancialTrxn', 'payment_instrument_id', $params['payment_instrument_id']); diff --git a/www/modules/civicrm/CRM/Financial/Form/SalesTaxTrait.php b/www/modules/civicrm/CRM/Financial/Form/SalesTaxTrait.php index 01f0949e1..6c7661c80 100644 --- a/www/modules/civicrm/CRM/Financial/Form/SalesTaxTrait.php +++ b/www/modules/civicrm/CRM/Financial/Form/SalesTaxTrait.php @@ -68,7 +68,7 @@ public function getTaxRatesForFinancialTypes() { * @return string */ public function getTaxRateForFinancialType($financialTypeID) { - return CRM_Utils_Array::value($financialTypeID, $this->getTaxRatesForFinancialTypes()); + return $this->getTaxRatesForFinancialTypes()[$financialTypeID] ?? NULL; } } diff --git a/www/modules/civicrm/CRM/Friend/DAO/Friend.php b/www/modules/civicrm/CRM/Friend/DAO/Friend.php index a8f1583ed..ec7661813 100644 --- a/www/modules/civicrm/CRM/Friend/DAO/Friend.php +++ b/www/modules/civicrm/CRM/Friend/DAO/Friend.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Friend/Friend.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:c9b5f756669b6fbb10e7c112d55b4c14) + * (GenCodeChecksum:2b203841aada2a3bf5ea271d2f5ff05f) */ /** diff --git a/www/modules/civicrm/CRM/Friend/Form.php b/www/modules/civicrm/CRM/Friend/Form.php index c36056d04..2697e0d0b 100644 --- a/www/modules/civicrm/CRM/Friend/Form.php +++ b/www/modules/civicrm/CRM/Friend/Form.php @@ -139,9 +139,9 @@ public function setDefaultValues() { CRM_Friend_BAO_Friend::getValues($defaults); $this->setTitle(CRM_Utils_Array::value('title', $defaults)); - $this->assign('title', CRM_Utils_Array::value('title', $defaults)); - $this->assign('intro', CRM_Utils_Array::value('intro', $defaults)); - $this->assign('message', CRM_Utils_Array::value('suggested_message', $defaults)); + $this->assign('title', $defaults['title'] ?? NULL); + $this->assign('intro', $defaults['intro'] ?? NULL); + $this->assign('message', $defaults['suggested_message'] ?? NULL); $this->assign('entityID', $this->_entityId); list($fromName, $fromEmail) = CRM_Contact_BAO_Contact::getContactDetails($this->_contactID); diff --git a/www/modules/civicrm/CRM/Group/Form/Edit.php b/www/modules/civicrm/CRM/Group/Form/Edit.php index e9064efc0..632a45fb3 100644 --- a/www/modules/civicrm/CRM/Group/Form/Edit.php +++ b/www/modules/civicrm/CRM/Group/Form/Edit.php @@ -21,6 +21,7 @@ class CRM_Group_Form_Edit extends CRM_Core_Form { use CRM_Core_Form_EntityFormTrait; + use CRM_Custom_Form_CustomDataTrait; /** * The group object, if an id is present @@ -43,13 +44,6 @@ class CRM_Group_Form_Edit extends CRM_Core_Form { */ protected $_groupValues; - /** - * What blocks should we show and hide. - * - * @var CRM_Core_ShowHideBlocks - */ - protected $_showHide; - /** * The civicrm_group_organization table id * @@ -60,7 +54,7 @@ class CRM_Group_Form_Edit extends CRM_Core_Form { /** * Set entity fields to be assigned to the form. */ - protected function setEntityFields() { + protected function setEntityFields(): void { $this->entityFields = [ 'frontend_title' => ['name' => 'frontend_title', 'required' => TRUE], 'frontend_description' => ['name' => 'frontend_description'], @@ -131,6 +125,7 @@ public function preProcess() { } } $this->assign('count', $count ?? NULL); + $this->assign('smartGroupsUsingThisGroup', CRM_Contact_BAO_SavedSearch::getSmartGroupsUsingGroup($this->_id)); $this->setTitle(ts('Confirm Group Delete')); } if ($this->_groupValues['is_reserved'] == 1 && !CRM_Core_Permission::check('administer reserved groups')) { @@ -159,9 +154,6 @@ public function preProcess() { $session->pushUserContext(CRM_Utils_System::url('civicrm/group', 'reset=1')); } $this->addExpectedSmartyVariables(['freezeMailingList', 'hideMailingList']); - - //build custom data - CRM_Custom_Form_CustomData::preProcess($this, NULL, NULL, 1, 'Group', $this->_id); } /** @@ -201,14 +193,11 @@ public function setDefaultValues() { } } - $parentGroupIds = explode(',', $this->_groupValues['parents']); + $parentGroupIds = explode(',', ($this->_groupValues['parents'] ?? '')); $defaults['parents'] = $parentGroupIds; if (empty($defaults['parents'])) { $defaults['parents'] = CRM_Core_BAO_Domain::getGroupId(); } - - // custom data set defaults - $defaults += CRM_Custom_Form_CustomData::setDefaultValues($this); return $defaults; } @@ -216,7 +205,7 @@ public function setDefaultValues() { * Build the form object. */ public function buildQuickForm() { - self::buildQuickEntityForm(); + $this->buildQuickEntityForm(); if ($this->_action & CRM_Core_Action::DELETE) { return; } @@ -253,8 +242,9 @@ public function buildQuickForm() { } $this->addElement('checkbox', 'is_active', ts('Is active?')); - //build custom data - CRM_Custom_Form_CustomData::buildQuickForm($this); + if ($this->isSubmitted()) { + $this->addCustomDataFieldsToForm('Group'); + } $options = [ 'selfObj' => $this, @@ -271,7 +261,7 @@ public function buildQuickForm() { * @param array $fileParams * @param array $options * - * @return array + * @return array|true * list of errors to be posted back to the form */ public static function formRule($fields, $fileParams, $options) { @@ -333,7 +323,7 @@ public function postProcess() { $params['is_reserved'] ??= FALSE; $params['is_active'] ??= FALSE; - $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params, + $params['custom'] = CRM_Core_BAO_CustomField::postProcess($this->getSubmittedValues(), $this->_id, 'Group' ); @@ -380,7 +370,7 @@ public static function buildParentGroups(&$form) { } } } - $form->assign_by_ref('parent_groups', $parentGroupElements); + $form->assign('parent_groups', $parentGroupElements); if (isset($form->_id)) { $potentialParentGroupIds = CRM_Contact_BAO_GroupNestingCache::getPotentialCandidates($form->_id, $groupNames); @@ -421,7 +411,7 @@ public static function buildParentGroups(&$form) { * * @param CRM_Core_Form $form */ - public static function buildGroupOrganizations(&$form) { + public static function buildGroupOrganizations($form) { if (CRM_Core_Permission::check('administer Multiple Organizations') && CRM_Core_Permission::isMultisiteEnabled()) { //group organization Element $props = ['api' => ['params' => ['contact_type' => 'Organization']]]; diff --git a/www/modules/civicrm/CRM/Import/Controller.php b/www/modules/civicrm/CRM/Import/Controller.php new file mode 100644 index 000000000..b8db4446d --- /dev/null +++ b/www/modules/civicrm/CRM/Import/Controller.php @@ -0,0 +1,55 @@ +entity; + } + + /** + * Class constructor. + * + * @param string $title + * @param array $arguments + * + * @throws \CRM_Core_Exception + */ + public function __construct(string $title, array $arguments) { + parent::__construct($title, TRUE); + set_time_limit(0); + + if (!empty($arguments['entity'])) { + $this->entity = $arguments['entity']; + } + else { + $pathArguments = explode('/', (CRM_Utils_System::currentPath() ?: '')); + unset($pathArguments[0], $pathArguments[1]); + $this->entity = CRM_Utils_String::convertStringToCamel(implode('_', $pathArguments)); + } + $this->_stateMachine = new CRM_Import_StateMachine($this, TRUE, $this->entity, $arguments['class_prefix'] ?? NULL); + // 1 (or TRUE) has been the action passed historically - but it is probably meaningless. + $this->addPages($this->_stateMachine, CRM_Core_Action::ADD); + $config = CRM_Core_Config::singleton(); + $this->addActions($config->uploadDir, ['uploadFile']); + } + +} diff --git a/www/modules/civicrm/CRM/Import/DataSource.php b/www/modules/civicrm/CRM/Import/DataSource.php index f6ddab89f..e60bf3021 100644 --- a/www/modules/civicrm/CRM/Import/DataSource.php +++ b/www/modules/civicrm/CRM/Import/DataSource.php @@ -34,6 +34,18 @@ abstract class CRM_Import_DataSource implements DataSourceInterface { */ private $limit; + public function getLimit(): int { + return $this->limit; + } + + public function getOffset(): int { + return $this->offset; + } + + public function getStatuses(): array { + return $this->statuses; + } + /** * @param int $limit * @@ -41,7 +53,7 @@ abstract class CRM_Import_DataSource implements DataSourceInterface { */ public function setLimit(int $limit): DataSourceInterface { $this->limit = $limit; - $this->queryResultObject = NULL; + $this->flushQueryResults(); return $this; } @@ -52,7 +64,7 @@ public function setLimit(int $limit): DataSourceInterface { */ public function setOffset(int $offset): CRM_Import_DataSource { $this->offset = $offset; - $this->queryResultObject = NULL; + $this->flushQueryResults(); return $this; } @@ -137,7 +149,7 @@ public function getAggregateFields(): ?array { */ public function setStatuses(array $statuses): DataSourceInterface { $this->statuses = $statuses; - $this->queryResultObject = NULL; + $this->flushQueryResults(); return $this; } @@ -192,6 +204,19 @@ public function getRow(): ?array { return $values; } + /** + * Flush the existing query to retrieve rows. + * + * The query will be run again, potentially retrieving newly-available rows. + * Note the 'newly available' could mean an external process has intervened. + * For example the import_extensions lazy-loads into the import table. + * + * @return void + */ + private function flushQueryResults() { + $this->queryResultObject = NULL; + } + /** * Get row count. * diff --git a/www/modules/civicrm/CRM/Import/Form/MapField.php b/www/modules/civicrm/CRM/Import/Form/MapField.php index 2eda66a1c..b5f372fd5 100644 --- a/www/modules/civicrm/CRM/Import/Form/MapField.php +++ b/www/modules/civicrm/CRM/Import/Form/MapField.php @@ -69,7 +69,7 @@ public function preProcess() { $this->addExpectedSmartyVariables(['highlightedRelFields', 'initHideBoxes']); $this->assign('columnNames', $this->getColumnHeaders()); $this->assign('showColumnNames', $this->getSubmittedValue('skipColumnHeader') || $this->getSubmittedValue('dataSource') !== 'CRM_Import_DataSource'); - $this->assign('highlightedFields', $this->getHighlightedFields()); + $this->assign('highlightedFields', json_encode($this->getHighlightedFields())); $this->assign('dataValues', array_values($this->getDataRows([], 2))); $this->_mapperFields = $this->getAvailableFields(); $fieldMappings = $this->getFieldMappings(); diff --git a/www/modules/civicrm/CRM/Import/Forms.php b/www/modules/civicrm/CRM/Import/Forms.php index 81989b595..784cb7889 100644 --- a/www/modules/civicrm/CRM/Import/Forms.php +++ b/www/modules/civicrm/CRM/Import/Forms.php @@ -82,6 +82,10 @@ public function getUserJobType(): string { return ''; } + public function getEntity() { + return $this->controller->getEntity(); + } + /** * @return int|null */ @@ -850,17 +854,28 @@ protected function getHighlightedFields(): array { public function getHeaderPatterns(): array { $headerPatterns = []; foreach ($this->getFields() as $name => $field) { - if (empty($field['headerPattern']) || $field['headerPattern'] === '//') { - continue; + if (!empty($field['usage']['import']) && !empty($field['title'])) { + $patterns = [ + $this->strToPattern($field['name']), + $this->strToPattern($field['title']), + ]; + if (!empty($field['html']['label'])) { + $patterns[] = $this->strToPattern($field['html']['label']); + } + // Swap out dots for double underscores so as not to break the quick form js. + // We swap this back on postProcess. + $name = str_replace('.', '__', $name); + $headerPatterns[$name] = '/^' . implode('|', array_unique($patterns)) . '$/i'; } - // Swap out dots for double underscores so as not to break the quick form js. - // We swap this back on postProcess. - $name = str_replace('.', '__', $name); - $headerPatterns[$name] = $field['headerPattern']; } return $headerPatterns; } + private function strToPattern(string $str) { + $str = str_replace(['_', '-'], ' ', $str); + return strtolower(str_replace(' ', '[-_ ]?', preg_quote($str, '/'))); + } + /** * Has the user chosen to update existing records. * @return bool diff --git a/www/modules/civicrm/CRM/Import/ImportProcessor.php b/www/modules/civicrm/CRM/Import/ImportProcessor.php index 4938dff9b..daf10f230 100644 --- a/www/modules/civicrm/CRM/Import/ImportProcessor.php +++ b/www/modules/civicrm/CRM/Import/ImportProcessor.php @@ -165,15 +165,12 @@ public function getMetadata(): array { * @throws \CRM_Core_Exception */ public function setMetadata(array $metadata) { - $fieldDetails = civicrm_api3('CustomField', 'get', [ - 'return' => ['custom_group_id.title'], - 'options' => ['limit' => 0], - ])['values']; foreach ($metadata as $index => $field) { if (!empty($field['custom_field_id'])) { + $fieldDetails = CRM_Core_BAO_CustomField::getField($field['custom_field_id']); // The 'label' format for import is custom group title :: custom name title $metadata[$index]['name'] = $index; - $metadata[$index]['title'] .= ' :: ' . $fieldDetails[$field['custom_field_id']]['custom_group_id.title']; + $metadata[$index]['title'] .= ' :: ' . $fieldDetails['custom_group']['title']; } } $this->metadata = $metadata; diff --git a/www/modules/civicrm/CRM/Import/Parser.php b/www/modules/civicrm/CRM/Import/Parser.php index 84fb79804..dd410fd6d 100644 --- a/www/modules/civicrm/CRM/Import/Parser.php +++ b/www/modules/civicrm/CRM/Import/Parser.php @@ -30,33 +30,17 @@ * supported. */ abstract class CRM_Import_Parser implements UserJobInterface { - /** - * Settings - */ - const MAX_WARNINGS = 25, DEFAULT_TIMEOUT = 30; /** * Return codes */ const VALID = 1, WARNING = 2, ERROR = 4, CONFLICT = 8, STOP = 16, DUPLICATE = 32, MULTIPLE_DUPE = 64, NO_MATCH = 128, UNPARSED_ADDRESS_WARNING = 256; - /** - * Parser modes - */ - const MODE_MAPFIELD = 1, MODE_PREVIEW = 2, MODE_SUMMARY = 4, MODE_IMPORT = 8; - /** * Codes for duplicate record handling */ const DUPLICATE_SKIP = 1, DUPLICATE_UPDATE = 4, DUPLICATE_FILL = 8, DUPLICATE_NOCHECK = 16; - /** - * Contact types - * - * @deprecated - */ - const CONTACT_INDIVIDUAL = 'Individual', CONTACT_HOUSEHOLD = 'Household', CONTACT_ORGANIZATION = 'Organization'; - /** * User job id. * @@ -223,63 +207,12 @@ public function getContactSubType(): ?string { return $this->_contactSubType; } - /** - * Total number of non empty lines - * @var int - */ - protected $_totalCount; - - /** - * Running total number of valid lines - * @var int - */ - protected $_validCount; - - /** - * Running total number of invalid rows - * @var int - */ - protected $_invalidRowCount; - /** * Array of error lines, bounded by MAX_ERROR * @var array */ protected $_errors; - /** - * Total number of duplicate (from database) lines - * @var int - */ - protected $_duplicateCount; - - /** - * Array of duplicate lines - * @var array - */ - protected $_duplicates; - - /** - * Maximum number of warnings to store - * @var int - */ - protected $_maxWarningCount = self::MAX_WARNINGS; - - /** - * Array of warning lines, bounded by MAX_WARNING - * @var array - */ - protected $_warnings; - - /** - * TO BE REMOVED. - * - * Array of all the fields that could potentially be part - * of this import process - * @var array - */ - private $_fields; - private $dedupeRules = []; /** @@ -332,6 +265,18 @@ protected function getContactFields(string $contactType): array { return $contactFields; } + /** + * @param string $entity + * + * @return array + * @throws \CRM_Core_Exception + */ + protected function getImportFieldsForEntity(string $entity): array { + return (array) civicrm_api4($entity, 'getFields', [ + 'where' => [['usage', 'CONTAINS', 'import']], + ])->indexBy('name'); + } + /** * Gets the fields available for importing in a key-name, title format. * @@ -406,13 +351,6 @@ protected function isFillDuplicates(): bool { return ((int) $this->getSubmittedValue('onDuplicate')) === CRM_Import_Parser::DUPLICATE_FILL; } - /** - * Cache of preview rows - * - * @var array - */ - protected $_rows; - /** * Contact type * @@ -522,11 +460,12 @@ public function getFieldsWhichSupportLocationTypes(): array { /** * Do this work on the form layer. * - * @deprecated + * @deprecated in 5.54 will be removed around 5.80 * * @return array */ public function getHeaderPatterns(): array { + CRM_Core_Error::deprecatedFunctionWarning('CRM_Import_Forms::getHeaderPatterns'); $values = []; foreach ($this->importableFieldsMetadata as $name => $field) { if (isset($field['headerPattern'])) { @@ -675,7 +614,11 @@ public function queue(): void { // The retry limit for the queue is set to 5 - allowing for a few deadlocks but we might consider // making this configurable at some point. $queue = Civi::queue('user_job_' . $this->getUserJobID(), ['type' => 'Sql', 'error' => 'abort', 'runner' => 'task', 'user_job_id' => $this->getUserJobID(), 'retry_limit' => 5]); - UserJob::update(FALSE)->setValues(['queue_id.name' => 'user_job_' . $this->getUserJobID()])->addWhere('id', '=', $this->getUserJobID())->execute(); + UserJob::update(FALSE) + ->setValues([ + 'queue_id.name' => 'user_job_' . $this->getUserJobID(), + 'status_id:name' => 'scheduled', + ])->addWhere('id', '=', $this->getUserJobID())->execute(); $offset = 0; $batchSize = Civi::settings()->get('import_batch_size'); while ($totalRows > 0) { @@ -1073,6 +1016,7 @@ private function _civicrm_api3_deprecated_add_formatted_param(&$values, &$params } if (isset($values['individual_prefix'])) { + CRM_Core_Error::deprecatedWarning('code should be unreachable, slated for removal'); if (!empty($params['prefix_id'])) { $prefixes = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'prefix_id'); $params['prefix'] = $prefixes[$params['prefix_id']]; @@ -1084,6 +1028,7 @@ private function _civicrm_api3_deprecated_add_formatted_param(&$values, &$params } if (isset($values['individual_suffix'])) { + CRM_Core_Error::deprecatedWarning('code should be unreachable, slated for removal'); if (!empty($params['suffix_id'])) { $suffixes = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'suffix_id'); $params['suffix'] = $suffixes[$params['suffix_id']]; @@ -1095,6 +1040,7 @@ private function _civicrm_api3_deprecated_add_formatted_param(&$values, &$params } if (isset($values['gender'])) { + CRM_Core_Error::deprecatedWarning('code should be unreachable, slated for removal'); if (!empty($params['gender_id'])) { $genders = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'gender_id'); $params['gender'] = $genders[$params['gender_id']]; @@ -1636,27 +1582,25 @@ protected function getTransformedFieldValue(string $fieldName, $importedValue) { $comparisonValue = $this->getComparisonValue($importedValue); return $options[$comparisonValue] ?? 'invalid_import_value'; } - if (!empty($fieldMetadata['FKClassName']) || ($fieldMetadata['pseudoconstant']['prefetch'] ?? NULL) === 'disabled') { - // @todo - make this generic - for fields where getOptions doesn't fetch - // getOptions does not retrieve these fields with high potential results - if ($fieldName === 'event_id') { - if (!isset(Civi::$statics[__CLASS__][$fieldName][$importedValue])) { - $event = Event::get()->addClause('OR', ['title', '=', $importedValue], ['id', '=', $importedValue])->addSelect('id')->execute()->first(); - Civi::$statics[__CLASS__][$fieldName][$importedValue] = $event['id'] ?? FALSE; - } - return Civi::$statics[__CLASS__][$fieldName][$importedValue] ?? 'invalid_import_value'; + // @todo - make this generic - for fields where getOptions doesn't fetch + // getOptions does not retrieve these fields with high potential results + if ($fieldName === 'event_id') { + if (!isset(Civi::$statics[__CLASS__][$fieldName][$importedValue])) { + $event = Event::get()->addClause('OR', ['title', '=', $importedValue], ['id', '=', $importedValue])->addSelect('id')->execute()->first(); + Civi::$statics[__CLASS__][$fieldName][$importedValue] = $event['id'] ?? FALSE; } - if ($fieldMetadata['name'] === 'campaign_id') { - if (!isset(Civi::$statics[__CLASS__][$fieldName][$importedValue])) { - $campaign = Campaign::get()->addClause('OR', ['title', '=', $importedValue], ['name', '=', $importedValue], ['id', '=', $importedValue])->addSelect('id')->execute()->first(); - Civi::$statics[__CLASS__][$fieldName][$importedValue] = $campaign['id'] ?? FALSE; - } - return Civi::$statics[__CLASS__][$fieldName][$importedValue] ?: 'invalid_import_value'; + return Civi::$statics[__CLASS__][$fieldName][$importedValue] ?? 'invalid_import_value'; + } + if ($fieldMetadata['name'] === 'campaign_id') { + if (!isset(Civi::$statics[__CLASS__][$fieldName][$importedValue])) { + $campaign = Campaign::get()->addClause('OR', ['title', '=', $importedValue], ['name', '=', $importedValue], ['id', '=', $importedValue])->addSelect('id')->execute()->first(); + Civi::$statics[__CLASS__][$fieldName][$importedValue] = $campaign['id'] ?? FALSE; } + return Civi::$statics[__CLASS__][$fieldName][$importedValue] ?: 'invalid_import_value'; } if ($dataType === 'Integer') { // We have resolved the options now so any remaining ones should be integers. - return CRM_Utils_Rule::numeric($importedValue) ? $importedValue : 'invalid_import_value'; + return CRM_Utils_Rule::numeric($importedValue) ? (int) $importedValue : 'invalid_import_value'; } return $importedValue; } @@ -1726,12 +1670,8 @@ protected function getFieldMetadata(string $fieldName, bool $loadOptions = FALSE } else { if (!empty($fieldMetadata['custom_group_id'])) { - $customField = CustomField::get(FALSE) - ->addWhere('id', '=', $fieldMetadata['custom_field_id']) - ->addSelect('name', 'custom_group_id.name') - ->execute() - ->first(); - $optionFieldName = $customField['custom_group_id.name'] . '.' . $customField['name']; + $customField = CRM_Core_BAO_CustomField::getField($fieldMetadata['custom_field_id']); + $optionFieldName = $customField['custom_group']['name'] . '.' . $customField['name']; } $options = civicrm_api4($this->getFieldEntity($fieldName), 'getFields', [ 'loadOptions' => ['id', 'name', 'label', 'abbr'], @@ -2107,7 +2047,17 @@ protected function getFieldMappings(): array { * @throws \CRM_Core_Exception */ public static function runJob(\CRM_Queue_TaskContext $taskContext, int $userJobID, int $limit, int $offset): bool { - $userJob = UserJob::get()->addWhere('id', '=', $userJobID)->addSelect('job_type')->execute()->first(); + $userJob = UserJob::get()->addWhere('id', '=', $userJobID) + ->addSelect('job_type', 'start_date')->execute()->first(); + if (!$userJob['start_date']) { + UserJob::update(FALSE) + ->setValues([ + 'status_id:name' => 'in_progress', + 'start_date' => 'now', + ]) + ->addWhere('id', '=', $userJob['id']) + ->execute(); + } $parserClass = NULL; foreach (CRM_Core_BAO_UserJob::getTypes() as $userJobType) { if ($userJob['job_type'] === $userJobType['id']) { @@ -2278,6 +2228,21 @@ protected function getPossibleMatchesByDedupeRule(array $params, $dedupeRuleID = return $matchIDs; } + /** + * @throws \CRM_Core_Exception + */ + protected function checkEntityExists(string $entity, int $id) { + try { + civicrm_api4($entity, 'get', ['where' => [['id', '=', $id]], 'select' => ['id']])->single(); + } + catch (CRM_Core_Exception $e) { + throw new CRM_Core_Exception(ts('%1 record not found for id %2', [ + 1 => $entity, + 2 => $id, + ])); + } + } + /** * Get the Api4 name of a custom field. * diff --git a/www/modules/civicrm/CRM/Import/StateMachine.php b/www/modules/civicrm/CRM/Import/StateMachine.php index ddc69485b..f70588986 100644 --- a/www/modules/civicrm/CRM/Import/StateMachine.php +++ b/www/modules/civicrm/CRM/Import/StateMachine.php @@ -17,23 +17,53 @@ /** * State machine for managing different states of the Import process. + * + * @internal */ class CRM_Import_StateMachine extends CRM_Core_StateMachine { + /** + * Get the entity name. + * + * @var string + */ + protected string $entity; + + private string $classPrefix; + + public function getEntity(): string { + return $this->entity; + } + /** * Class constructor. * - * @param object $controller - * @param \const|int $action + * @param CRM_Import_Controller $controller + * @param int $action + * @param ?string $entity + * @param string|null $classPrefix + * When the class name does not easily map to the prefix - ie the Custom import class. + * + * @internal only supported for core use. */ - public function __construct($controller, $action = CRM_Core_Action::NONE) { + public function __construct($controller, $action = CRM_Core_Action::NONE, ?string $entity = NULL, ?string $classPrefix = NULL) { parent::__construct($controller, $action); - - $classType = str_replace('_Controller', '', get_class($controller)); + $this->entity = ucfirst((string) $entity); + if ($classPrefix) { + $this->classPrefix = $classPrefix; + } + elseif ($this->entity) { + $entityPath = explode('_', CRM_Core_DAO_AllCoreTables::getDAONameForEntity($this->entity)); + $this->classPrefix = $entityPath[0] . '_' . $entityPath[1] . '_Import'; + } + else { + CRM_Core_Error::deprecatedWarning('entity parameter expected, always passed in core & few outside core uses so this will go'); + $this->classPrefix = str_replace('_Controller', '', get_class($controller)); + } $this->_pages = [ - $classType . '_Form_DataSource' => NULL, - $classType . '_Form_MapField' => NULL, - $classType . '_Form_Preview' => NULL, + $this->classPrefix . '_Form_DataSource' => NULL, + $this->classPrefix . '_Form_MapField' => NULL, + $this->classPrefix . '_Form_Preview' => NULL, ]; $this->addSequentialPages($this->_pages); diff --git a/www/modules/civicrm/CRM/Logging/Differ.php b/www/modules/civicrm/CRM/Logging/Differ.php index 083a6266f..f7c587b1b 100644 --- a/www/modules/civicrm/CRM/Logging/Differ.php +++ b/www/modules/civicrm/CRM/Logging/Differ.php @@ -219,7 +219,7 @@ private function diffsInTableForId($table, $id) { continue; } - if (($original[$diff] ?? NULL) === CRM_Utils_Array::value($diff, $changed)) { + if (($original[$diff] ?? NULL) === ($changed[$diff] ?? NULL)) { continue; } diff --git a/www/modules/civicrm/CRM/Logging/ReportDetail.php b/www/modules/civicrm/CRM/Logging/ReportDetail.php index 8df6ec89e..2599ea715 100644 --- a/www/modules/civicrm/CRM/Logging/ReportDetail.php +++ b/www/modules/civicrm/CRM/Logging/ReportDetail.php @@ -175,6 +175,9 @@ protected function convertDiffsToRows() { return []; } + // $cfDataTypesToBeFormatted corresponds to values in the db column civicrm_custom_field.data_type + $cfDataTypesToBeFormatted = array("Int", "ContactReference", "EntityReference"); + // populate $rows with only the differences between $changed and $original (skipping certain columns and NULL ↔ empty changes unless raw requested) $skipped = ['id']; $nRows = $rows = []; @@ -183,7 +186,7 @@ protected function convertDiffsToRows() { if (empty($metadata[$table])) { list($metadata[$table]['titles'], $metadata[$table]['values']) = $this->differ->titlesAndValuesForTable($table, $diff['log_date']); } - $values = CRM_Utils_Array::value('values', $metadata[$diff['table']], []); + $values = $metadata[$diff['table']]['values'] ?? []; $titles = $metadata[$diff['table']]['titles']; $field = $diff['field']; $from = $diff['from']; @@ -218,25 +221,67 @@ protected function convertDiffsToRows() { $to = implode(', ', array_filter($tos)); } + $cfArray = []; $tableDAOClass = CRM_Core_DAO_AllCoreTables::getClassForTable($table); + $fkClassName = NULL; if (!empty($tableDAOClass)) { $tableDAOFields = (new $tableDAOClass())->fields(); // If this field is a foreign key, then we can later use the foreign // class to translate the id into something more useful for display. $fkClassName = $tableDAOFields[$field]['FKClassName'] ?? NULL; } + else { + // Since this table didn't match a core table, check if it's a custom field. + $customGroup = CRM_Core_BAO_CustomGroup::getGroup(['table_name' => $table]); + foreach ($customGroup['fields'] ?? [] as $customField) { + if ($customField['column_name'] === $field) { + $cfArray = $customField; + break; + } + } + } if (isset($values[$field][$from])) { $from = $values[$field][$from]; } elseif (!empty($from) && !empty($fkClassName)) { $from = $this->convertForeignKeyValuesToLabels($fkClassName, $field, $from); } + elseif (!empty($from) && is_numeric($from) && array_key_exists("id", $cfArray) && is_int($cfArray["id"])) { + // Translate the id into something more useful for display, namely for id's that refer to option values and contacts. + $fromAsArray = civicrm_api3('CustomValue', 'getdisplayvalue', [ + 'entity_id' => $this->cid, + 'custom_field_id' => $cfArray["id"], + 'custom_field_value' => $from, + ]); + if (array_key_exists("data_type", $cfArray) && in_array($cfArray["data_type"], $cfDataTypesToBeFormatted)) { + $from = $this->formatLabelAndIdForDisplay($fromAsArray['values'][$cfArray["id"]]['display'], $from); + } + elseif (!empty($fromAsArray['values'][$cfArray["id"]]['display'])) { + $from = $fromAsArray['values'][$cfArray["id"]]['display']; + } + } + if (isset($values[$field][$to])) { $to = $values[$field][$to]; } elseif (!empty($to) && !empty($fkClassName)) { $to = $this->convertForeignKeyValuesToLabels($fkClassName, $field, $to); } + elseif (!empty($to) && is_numeric($to) && array_key_exists("id", $cfArray) && is_int($cfArray["id"])) { + // Translate the id into something more useful for display, namely for id's that refer to option values and contacts. + $toAsArray = civicrm_api3('CustomValue', 'getdisplayvalue', [ + 'entity_id' => $this->cid, + 'custom_field_id' => $cfArray["id"], + 'custom_field_value' => $to, + ]); + if (array_key_exists("data_type", $cfArray) && in_array($cfArray["data_type"], $cfDataTypesToBeFormatted)) { + $to = $this->formatLabelAndIdForDisplay($toAsArray['values'][$cfArray["id"]]['display'], $to); + } + elseif (!empty($toAsArray['values'][$cfArray["id"]]['display'])) { + $to = $toAsArray['values'][$cfArray["id"]]['display']; + } + } + if (isset($titles[$field])) { $field = $titles[$field]; } @@ -480,12 +525,23 @@ public function getLimit($rowCount = self::ROW_COUNT_LIMIT) { * @return string */ private function convertForeignKeyValuesToLabels(string $fkClassName, string $field, int $keyval): string { - if ($fkClassName::$_labelField) { - $labelValue = CRM_Core_DAO::getFieldValue($fkClassName, $keyval, $fkClassName::$_labelField); + if ($fkClassName::getLabelField()) { + $labelValue = CRM_Core_DAO::getFieldValue($fkClassName, $keyval, $fkClassName::getLabelField()); // Not sure if this should use ts - there's not a lot of context (`%1 (id: %2)`) - and also the similar field labels above don't use ts. return "{$labelValue} (id: {$keyval})"; } return (string) $keyval; } + /** + * Return a string with the label and value combined. + * + * @param string $labelValue + * @param int $keyval + * @return string + */ + private function formatLabelAndIdForDisplay(string $labelValue, int $keyval): string { + return empty($labelValue) ? (string) $keyval : "{$labelValue} (id: {$keyval})"; + } + } diff --git a/www/modules/civicrm/CRM/Logging/Schema.php b/www/modules/civicrm/CRM/Logging/Schema.php index 1baedd577..f7bffb4e2 100644 --- a/www/modules/civicrm/CRM/Logging/Schema.php +++ b/www/modules/civicrm/CRM/Logging/Schema.php @@ -50,7 +50,7 @@ class CRM_Logging_Schema { * @var array */ private $exceptions = [ - 'civicrm_job' => ['last_run'], + 'civicrm_job' => ['last_run', 'last_run_end'], 'civicrm_group' => ['cache_date', 'refresh_date'], ]; @@ -203,13 +203,11 @@ public function customDataLogTables() { */ public function entityCustomDataLogTables($extends) { $customGroupTables = []; - $customGroupDAO = CRM_Core_BAO_CustomGroup::getAllCustomGroupsByBaseEntity($extends); - $customGroupDAO->find(); - while ($customGroupDAO->fetch()) { - // logging is disabled for the table (e.g by hook) then $this->logs[$customGroupDAO->table_name] + foreach (CRM_Core_BAO_CustomGroup::getAll(['extends' => $extends]) as $customGroup) { + // logging is disabled for the table (e.g by hook) then $this->logs[$customGroup['table_name']] // will be empty. - if (!empty($this->logs[$customGroupDAO->table_name])) { - $customGroupTables[$customGroupDAO->table_name] = $this->logs[$customGroupDAO->table_name]; + if (!empty($this->logs[$customGroup['table_name']])) { + $customGroupTables[$customGroup['table_name']] = $this->logs[$customGroup['table_name']]; } } return $customGroupTables; diff --git a/www/modules/civicrm/CRM/Mailing/ActionTokens.php b/www/modules/civicrm/CRM/Mailing/ActionTokens.php index ecb3be4dd..88fcf287e 100644 --- a/www/modules/civicrm/CRM/Mailing/ActionTokens.php +++ b/www/modules/civicrm/CRM/Mailing/ActionTokens.php @@ -83,9 +83,7 @@ public function evaluateToken( list($verp, $urls) = CRM_Mailing_BAO_Mailing::getVerpAndUrls( $row->context['mailingJobId'], $row->context['mailingActionTarget']['id'] ?? NULL, - $row->context['mailingActionTarget']['hash'] ?? NULL, - // Note: Behavior is already undefined for SMS/'phone' mailings... - $row->context['mailingActionTarget']['email'] ?? NULL + $row->context['mailingActionTarget']['hash'] ?? NULL ); $row->format('text/plain')->tokens($entity, $field, diff --git a/www/modules/civicrm/CRM/Mailing/BAO/Mailing.php b/www/modules/civicrm/CRM/Mailing/BAO/Mailing.php index 7288d0199..d76a63516 100644 --- a/www/modules/civicrm/CRM/Mailing/BAO/Mailing.php +++ b/www/modules/civicrm/CRM/Mailing/BAO/Mailing.php @@ -16,6 +16,7 @@ */ use Civi\API\Exception\UnauthorizedException; +use Civi\Api4\MailingGroup; require_once 'Mail/mime.php'; @@ -64,18 +65,6 @@ class CRM_Mailing_BAO_Mailing extends CRM_Mailing_DAO_Mailing implements \Civi\C */ private $footer = NULL; - /** - * The HTML content of the message. - * @var string - */ - private $html = NULL; - - /** - * The text content of the message. - * @var string - */ - private $text = NULL; - /** * Cached BAO for the domain. * @var int @@ -95,7 +84,6 @@ public static function getRecipients($mailingID) { $mailingObj->id = $mailingID; $mailingObj->find(TRUE); - $mailing = CRM_Mailing_BAO_Mailing::getTableName(); $contact = CRM_Contact_DAO_Contact::getTableName(); $isSMSmode = (!CRM_Utils_System::isNull($mailingObj->sms_provider_id)); @@ -109,7 +97,7 @@ public static function getRecipients($mailingID) { ->param('#mailing_id', $mailingID) ->execute(); while ($dao->fetch()) { - if ($dao->entity_table == 'civicrm_mailing') { + if ($dao->entity_table === 'civicrm_mailing') { $priorMailingIDs[$dao->group_type] = explode(',', $dao->group_ids); } else { @@ -145,10 +133,10 @@ public static function getRecipients($mailingID) { if ($groupDAO->cache_date == NULL || $groupDAO->is_hidden) { CRM_Contact_BAO_GroupContactCache::load($groupDAO); } - if ($groupType == 'Include') { + if ($groupType === 'Include') { $includeSmartGroupIDs[] = $groupDAO->id; } - elseif ($groupType == 'Exclude') { + elseif ($groupType === 'Exclude') { $excludeSmartGroupIDs[] = $groupDAO->id; } //NOTE: Do nothing for base @@ -452,31 +440,11 @@ protected static function processWorkflowPermissions(array $params): array { * * @param array $params * @param \CRM_Mailing_DAO_Mailing $mailing - * - * @return array */ - protected static function doSubmitActions(array $params, CRM_Mailing_DAO_Mailing $mailing): array { + protected static function doSubmitActions(array $params, CRM_Mailing_DAO_Mailing $mailing): void { // Create parent job if not yet created. // Condition on the existence of a scheduled date. - if (!empty($params['scheduled_date']) && $params['scheduled_date'] != 'null' && empty($params['_skip_evil_bao_auto_schedule_'])) { - - if (!isset($params['is_completed']) || $params['is_completed'] !== 1) { - $mailingGroups = \Civi\Api4\MailingGroup::get() - ->addSelect('group.id') - ->addJoin('Group AS group', 'LEFT', ['entity_id', '=', 'group.id']) - ->addWhere('mailing_id', '=', $mailing->id) - ->addWhere('entity_table', '=', 'civicrm_group') - ->addWhere('group_type', 'IN', ['Include', 'Exclude']) - ->addClause('OR', ['group.saved_search_id', 'IS NOT NULL'], ['group.children', 'IS NOT NULL']) - ->execute(); - foreach ($mailingGroups as $mailingGroup) { - CRM_Contact_BAO_GroupContactCache::invalidateGroupContactCache($mailingGroup['group.id']); - $group = new CRM_Contact_DAO_Group(); - $group->find(TRUE); - $group->id = $mailingGroup['group.id']; - CRM_Contact_BAO_GroupContactCache::load($group); - } - } + if (!empty($params['scheduled_date']) && $params['scheduled_date'] !== 'null' && empty($params['_skip_evil_bao_auto_schedule_'])) { $job = new CRM_Mailing_BAO_MailingJob(); $job->mailing_id = $mailing->id; @@ -497,9 +465,44 @@ protected static function doSubmitActions(array $params, CRM_Mailing_DAO_Mailing // Populate the recipients. if (empty($params['_skip_evil_bao_auto_recipients_'])) { + if ((!isset($params['is_completed']) || $params['is_completed'] !== 1) + && !empty($params['scheduled_date']) && $params['scheduled_date'] !== 'null' + && empty($params['_skip_evil_bao_auto_schedule_']) + ) { + self::refreshMailingGroupCache($mailing->id); + } self::getRecipients($mailing->id); } - return $params; + } + + /** + * Refresh the group cache for groups relevant to the mailing. + * + * @param int $mailingID + * + * @throws \CRM_Core_Exception + * @throws \Civi\API\Exception\UnauthorizedException + * + * @internal not supported for use from outside of core. Function has always + * been internal so may be moved / removed / alters at any time without + * regard for external users. + */ + public static function refreshMailingGroupCache(int $mailingID): void { + $mailingGroups = MailingGroup::get() + ->addSelect('group.id') + ->addJoin('Group AS group', 'LEFT', ['entity_id', '=', 'group.id']) + ->addWhere('mailing_id', '=', $mailingID) + ->addWhere('entity_table', '=', 'civicrm_group') + ->addWhere('group_type', 'IN', ['Include', 'Exclude']) + ->addClause('OR', ['group.saved_search_id', 'IS NOT NULL'], ['group.children', 'IS NOT NULL']) + ->execute(); + foreach ($mailingGroups as $mailingGroup) { + CRM_Contact_BAO_GroupContactCache::invalidateGroupContactCache($mailingGroup['group.id']); + $group = new CRM_Contact_DAO_Group(); + $group->find(TRUE); + $group->id = $mailingGroup['group.id']; + CRM_Contact_BAO_GroupContactCache::load($group); + } } /** @@ -933,21 +936,18 @@ public static function addMessageIdHeader(&$headers, $prefix, $job_id, $event_qu * ID of the EventQueue. * @param string $hash * Hash of the EventQueue. - * @param string $email - * Destination address. * * @return array * (reference) array array ref that hold array refs to the verp info and urls */ - public static function getVerpAndUrls($job_id, $event_queue_id, $hash, $email) { + public static function getVerpAndUrls($job_id, $event_queue_id, $hash) { // create a skeleton object and set its properties that are required by getVerpAndUrlsAndHeaders() - $config = CRM_Core_Config::singleton(); $bao = new CRM_Mailing_BAO_Mailing(); $bao->_domain = CRM_Core_BAO_Domain::getDomain(); $bao->from_name = $bao->from_email = $bao->subject = ''; // use $bao's instance method to get verp and urls - [$verp, $urls, $_] = $bao->getVerpAndUrlsAndHeaders($job_id, $event_queue_id, $hash, $email); + [$verp, $urls, $_] = $bao->getVerpAndUrlsAndHeaders($job_id, $event_queue_id, $hash); return [$verp, $urls]; } @@ -960,15 +960,11 @@ public static function getVerpAndUrls($job_id, $event_queue_id, $hash, $email) { * ID of the EventQueue. * @param string $hash * Hash of the EventQueue. - * @param string $email - * Destination address. - * - * @param bool $isForward * * @return array * array ref that hold array refs to the verp info, urls, and headers */ - public function getVerpAndUrlsAndHeaders($job_id, $event_queue_id, $hash, $email, $isForward = FALSE) { + public function getVerpAndUrlsAndHeaders($job_id, $event_queue_id, $hash) { $config = CRM_Core_Config::singleton(); /** @@ -1033,401 +1029,20 @@ public function getVerpAndUrlsAndHeaders($job_id, $event_queue_id, $hash, $email 'List-Unsubscribe' => "", ]; self::addMessageIdHeader($headers, 'm', $job_id, $event_queue_id, $hash); - if ($isForward) { - $headers['Subject'] = "[Fwd:{$this->subject}]"; - } return [&$verp, &$urls, &$headers]; } - /** - * Compose a message. - * - * @deprecated - * This is used by CiviMail but will be made redundant by FlexMailer/TokenProcessor. - * @param int $job_id - * ID of the Job associated with this message. - * @param int $event_queue_id - * ID of the EventQueue. - * @param string $hash - * Hash of the EventQueue. - * @param string $contactId - * ID of the Contact. - * @param string $email - * Destination address. - * @param string $recipient - * To: of the recipient. - * @param bool $test - * Is this mailing a test?. - * @param $contactDetails - * @param $attachments - * @param bool $isForward - * Is this mailing compose for forward?. - * @param string $fromEmail - * Email address of who is forwarding it. - * - * @param null $replyToEmail - * - * @return Mail_mime The mail object - */ - public function compose( - $job_id, $event_queue_id, $hash, $contactId, - $email, &$recipient, $test, - $contactDetails, &$attachments, $isForward = FALSE, - $fromEmail = NULL, $replyToEmail = NULL - ) { - $config = CRM_Core_Config::singleton(); - $this->getTokens(); - - if ($this->_domain == NULL) { - $this->_domain = CRM_Core_BAO_Domain::getDomain(); - } - - [$verp, $urls, $headers] = $this->getVerpAndUrlsAndHeaders( - $job_id, - $event_queue_id, - $hash, - $email, - $isForward - ); - - //set from email who is forwarding it and not original one. - if ($fromEmail) { - unset($headers['From']); - $headers['From'] = "<{$fromEmail}>"; - } - - if ($replyToEmail && ($fromEmail != $replyToEmail)) { - $headers['Reply-To'] = "{$replyToEmail}"; - } - - if ($contactDetails) { - $contact = $contactDetails; - } - elseif ($contactId === 0) { - //anonymous user - $contact = []; - CRM_Utils_Hook::tokenValues($contact, [$contactId], $job_id); - } - else { - $params = [['contact_id', '=', $contactId, 0, 0]]; - [$contact] = CRM_Contact_BAO_Query::apiQuery($params); - // $contact is an array of [ contactID => contactDetails ] - - // also call the hook to get contact details - CRM_Utils_Hook::tokenValues($contact, [$contactId], $job_id); - - // Don't send if contact doesn't exist - $contact = reset($contact); - if (!$contact || is_a($contact, 'CRM_Core_Error')) { - CRM_Core_Error::debug_log_message(ts('CiviMail will not send email to a non-existent contact: %1', - [1 => $contactId] - )); - // setting this because function is called by reference - //@todo test not calling function by reference - $res = NULL; - return $res; - } - } - - $pTemplates = $this->getPreparedTemplates(); - $pEmails = []; - - foreach ($pTemplates as $type => $pTemplate) { - $html = $type == 'html'; - $pEmails[$type] = []; - $pEmail = &$pEmails[$type]; - $template = &$pTemplates[$type]['template']; - $tokens = &$pTemplates[$type]['tokens']; - $idx = 0; - if (!empty($tokens)) { - foreach ($tokens as $idx => $token) { - $token_data = $this->getTokenData($token, $html, $contact, $verp, $urls, $event_queue_id); - array_push($pEmail, $template[$idx]); - array_push($pEmail, $token_data); - } - } - else { - array_push($pEmail, $template[$idx]); - } - - if (isset($template[($idx + 1)])) { - array_push($pEmail, $template[($idx + 1)]); - } - } - - $html = NULL; - if (isset($pEmails['html']) && is_array($pEmails['html']) && count($pEmails['html'])) { - $html = &$pEmails['html']; - } - - $text = NULL; - if (isset($pEmails['text']) && is_array($pEmails['text']) && count($pEmails['text'])) { - $text = &$pEmails['text']; - } - - // push the tracking url on to the html email if necessary - if ($this->open_tracking && $html) { - array_push($html, "\n" . '' - ); - } - - $message = new Mail_mime("\n"); - - $useSmarty = defined('CIVICRM_MAIL_SMARTY') && CIVICRM_MAIL_SMARTY; - if ($useSmarty) { - $smarty = CRM_Core_Smarty::singleton(); - // also add the contact tokens to the template - $smarty->assign_by_ref('contact', $contact); - } - - $mailParams = $headers; - if ($text) { - $textBody = implode('', $text); - if ($useSmarty) { - $textBody = $smarty->fetch("string:$textBody"); - } - $mailParams['text'] = $textBody; - } - - if ($html) { - $htmlBody = implode('', $html); - if ($useSmarty) { - $htmlBody = $smarty->fetch("string:$htmlBody"); - } - $mailParams['html'] = $htmlBody; - } - - if (empty($mailParams['text']) && empty($mailParams['html'])) { - // CRM-9833 - // something went wrong, lets log it and return null (by reference) - CRM_Core_Error::debug_log_message(ts('CiviMail will not send an empty mail body, Skipping: %1', - [1 => $email] - )); - $res = NULL; - return $res; - } - - $mailParams['attachments'] = $attachments; - - $mailParams['Subject'] = $pEmails['subject'] ?? NULL; - if (is_array($mailParams['Subject'])) { - $mailParams['Subject'] = implode('', $mailParams['Subject']); - } - - $mailParams['toName'] = CRM_Utils_Array::value('display_name', - $contact - ); - $mailParams['toEmail'] = $email; - - // Add job ID to mailParams for external email delivery service to utilise - $mailParams['job_id'] = $job_id; - - CRM_Utils_Hook::alterMailParams($mailParams, 'civimail'); - - // CRM-10699 support custom email headers - if (!empty($mailParams['headers'])) { - $headers = array_merge($headers, $mailParams['headers']); - } - //cycle through mailParams and set headers array - foreach ($mailParams as $paramKey => $paramValue) { - //exclude values not intended for the header - if (!in_array($paramKey, [ - 'text', - 'html', - 'attachments', - 'toName', - 'toEmail', - ]) - ) { - $headers[$paramKey] = $paramValue; - } - } - - if (!empty($mailParams['text'])) { - $message->setTxtBody($mailParams['text']); - } - - if (!empty($mailParams['html'])) { - $message->setHTMLBody($mailParams['html']); - } - - if (!empty($mailParams['attachments'])) { - foreach ($mailParams['attachments'] as $fileID => $attach) { - $message->addAttachment($attach['fullPath'], - $attach['mime_type'], - $attach['cleanName'] - ); - } - } - - //pickup both params from mail params. - $toName = trim($mailParams['toName']); - $toEmail = trim($mailParams['toEmail']); - if ($toName == $toEmail || - strpos($toName, '@') !== FALSE - ) { - $toName = NULL; - } - else { - $toName = CRM_Utils_Mail::formatRFC2822Name($toName); - } - - $headers['To'] = "$toName <$toEmail>"; - - $headers['Precedence'] = 'bulk'; - // Will test in the mail processor if the X-VERP is set in the bounced email. - // (As an option to replace real VERP for those that can't set it up) - $headers['X-CiviMail-Bounce'] = $verp['bounce']; - - //CRM-5058 - //token replacement of subject - $headers['Subject'] = $mailParams['Subject']; - - CRM_Utils_Mail::setMimeParams($message); - $headers = $message->headers($headers); - - //get formatted recipient - $recipient = $headers['To']; - - // make sure we unset a lot of stuff - unset($verp); - unset($urls); - unset($params); - unset($contact); - unset($ids); - - return $message; - } - - /** - * Replace tokens. - * - * Get mailing object and replaces subscribeInvite, domain and mailing tokens. - * - * @deprecated - * This is used by CiviMail but will be made redundant by FlexMailer/TokenProcessor. - * @param CRM_Mailing_BAO_Mailing $mailing - */ - public static function tokenReplace(&$mailing) { - $domain = CRM_Core_BAO_Domain::getDomain(); - - foreach (['text', 'html'] as $type) { - $tokens = $mailing->getTokens(); - if (isset($mailing->templates[$type])) { - $mailing->templates[$type] = CRM_Utils_Token::replaceSubscribeInviteTokens($mailing->templates[$type]); - $mailing->templates[$type] = CRM_Utils_Token::replaceDomainTokens( - $mailing->templates[$type], - $domain, - $type == 'html', - $tokens[$type] - ); - $mailing->templates[$type] = CRM_Utils_Token::replaceMailingTokens($mailing->templates[$type], $mailing, NULL, $tokens[$type]); - } - } - } - - /** - * Get data to resolve tokens. - * - * @deprecated - * This is used by CiviMail but will be made redundant by FlexMailer/TokenProcessor. - * - * @param array $token_a - * @param bool $html - * Whether to encode the token result for use in HTML email - * @param array $contact - * @param string $verp - * @param array $urls - * @param int $event_queue_id - * - * @return bool|mixed|null|string - */ - private function getTokenData(&$token_a, $html, &$contact, &$verp, &$urls, $event_queue_id) { - $type = $token_a['type']; - $token = $token_a['token']; - $data = $token; - - $useSmarty = defined('CIVICRM_MAIL_SMARTY') && CIVICRM_MAIL_SMARTY; - - if ($type == 'embedded_url') { - $embed_data = []; - foreach ($token as $t) { - $embed_data[] = $this->getTokenData($t, $html, $contact, $verp, $urls, $event_queue_id); - } - $numSlices = count($embed_data); - $url = ''; - for ($i = 0; $i < $numSlices; $i++) { - $embed_url_data = parse_url($embed_data[$i]); - if (!empty($embed_url_data['scheme'])) { - $token_a['embed_parts'][$i] = preg_replace("/href=\"(https*:\/\/)/", "href=\"", $token_a['embed_parts'][$i]); - } - $url .= "{$token_a['embed_parts'][$i]}{$embed_data[$i]}"; - } - if (isset($token_a['embed_parts'][$numSlices])) { - $url .= $token_a['embed_parts'][$numSlices]; - } - // add trailing quote since we've gobbled it up in a previous regex - // function getPatterns, line 431 - if (preg_match("/^href[ ]*=[ ]*'.*[^']$/", $url)) { - $url .= "'"; - } - elseif (preg_match('/^href[ ]*=[ ]*".*[^"]$/', $url)) { - $url .= '"'; - } - $data = $url; - // CRM-20206 Fix ampersand encoding in plain text emails - if (empty($html)) { - $data = CRM_Utils_String::unstupifyUrl($data); - } - } - elseif ($type == 'url') { - if ($this->url_tracking && !empty($this->id)) { - // ensure that Google CSS and any .css files are not tracked. - if (!(strpos($token, 'css?family') || strpos($token, '.css'))) { - $data = CRM_Mailing_BAO_MailingTrackableURL::getTrackerURL($token, $this->id, $event_queue_id); - if (!empty($html)) { - $data = htmlentities($data, ENT_NOQUOTES); - } - } - } - else { - $data = $token; - } - } - elseif ($type == 'contact') { - $data = CRM_Utils_Token::getContactTokenReplacement($token, $contact, FALSE, FALSE, $useSmarty); - } - elseif ($type == 'action') { - $data = CRM_Utils_Token::getActionTokenReplacement($token, $verp, $urls, $html); - } - elseif ($type == 'domain') { - $domain = CRM_Core_BAO_Domain::getDomain(); - $data = CRM_Utils_Token::getDomainTokenReplacement($token, $domain, $html); - } - elseif ($type == 'mailing') { - if ($token == 'name') { - $data = $this->name; - } - elseif ($token == 'group') { - $groups = $this->getGroupNames(); - $data = implode(', ', $groups); - } - } - else { - $data = $contact["{$type}.{$token}"] ?? NULL; - } - return $data; - } - /** * Return a list of group names for this mailing. Does not work with * prior-mailing targets. * + * @deprecated since 5.71 will be removed around 5.77 + * * @return array * Names of groups receiving this mailing */ public function &getGroupNames() { + CRM_Core_Error::deprecatedWarning('unused function'); if (!isset($this->id)) { return []; } @@ -1437,7 +1052,7 @@ public function &getGroupNames() { permissions in the future, but it's called by some extensions during mail processing, when cron isn't necessarily called with a logged-in user. */ - $mailingGroups = \Civi\Api4\MailingGroup::get(FALSE) + $mailingGroups = MailingGroup::get(FALSE) ->addSelect('group.title', 'group.frontend_title') ->addJoin('Group AS group', 'LEFT', ['entity_id', '=', 'group.id']) ->addWhere('mailing_id', '=', $this->id) @@ -1599,10 +1214,6 @@ public static function create(array $params) { $mailing = self::add($params); - if (is_a($mailing, 'CRM_Core_Error')) { - $transaction->rollback(); - return $mailing; - } // update mailings with hash values CRM_Contact_BAO_Contact_Utils::generateChecksum($mailing->id, NULL, NULL, NULL, 'mailing', 16); @@ -1643,8 +1254,8 @@ public static function create(array $params) { // In v4 of the api they are not available via CRUD. At some // point we will create a 'submit' function which will do the crud+submit // but for now only CRUD is available via v4 api. - if (($params['version'] ?? '') !== 4) { - $params = self::doSubmitActions($params, $mailing); + if (empty($params['skip_legacy_scheduling'])) { + self::doSubmitActions($params, $mailing); } return $mailing; @@ -1668,38 +1279,43 @@ public static function checkSendable($mailing) { } $mailing->copyValues($params); } - $errors = []; - foreach (['subject', 'name', 'from_name', 'from_email'] as $field) { - if (empty($mailing->{$field})) { - $errors[$field] = ts('Field "%1" is required.', [ - 1 => $field, - ]); + if ($mailing->sms_provider_id) { + if (empty($mailing->body_text)) { + $errors['body'] = ts('Field "body_text" is required.'); } } - if (empty($mailing->body_html) && empty($mailing->body_text)) { - $errors['body'] = ts('Field "body_html" or "body_text" is required.'); - } - - if (!Civi::settings()->get('disable_mandatory_tokens_check')) { - $header = $mailing->header_id && $mailing->header_id != 'null' ? CRM_Mailing_BAO_MailingComponent::findById($mailing->header_id) : NULL; - $footer = $mailing->footer_id && $mailing->footer_id != 'null' ? CRM_Mailing_BAO_MailingComponent::findById($mailing->footer_id) : NULL; - foreach (['body_html', 'body_text'] as $field) { + else { + foreach (['subject', 'name', 'from_name', 'from_email'] as $field) { if (empty($mailing->{$field})) { - continue; + $errors[$field] = ts('Field "%1" is required.', [ + 1 => $field, + ]); } - $str = ($header ? $header->{$field} : '') . $mailing->{$field} . ($footer ? $footer->{$field} : ''); - $err = CRM_Utils_Token::requiredTokens($str); - if ($err !== TRUE) { - foreach ($err as $token => $desc) { - $errors["{$field}:{$token}"] = ts('This message is missing a required token - {%1}: %2', - [1 => $token, 2 => $desc] - ); + } + if (empty($mailing->body_html) && empty($mailing->body_text)) { + $errors['body'] = ts('Field "body_html" or "body_text" is required.'); + } + + if (!Civi::settings()->get('disable_mandatory_tokens_check')) { + $header = $mailing->header_id && $mailing->header_id !== 'null' ? CRM_Mailing_BAO_MailingComponent::findById($mailing->header_id) : NULL; + $footer = $mailing->footer_id && $mailing->footer_id !== 'null' ? CRM_Mailing_BAO_MailingComponent::findById($mailing->footer_id) : NULL; + foreach (['body_html', 'body_text'] as $field) { + if (empty($mailing->{$field})) { + continue; + } + $str = ($header ? $header->{$field} : '') . $mailing->{$field} . ($footer ? $footer->{$field} : ''); + $err = CRM_Utils_Token::requiredTokens($str); + if ($err !== TRUE) { + foreach ($err as $token => $desc) { + $errors["{$field}:{$token}"] = ts('This message is missing a required token - {%1}: %2', + [1 => $token, 2 => $desc] + ); + } } } } } - return $errors; } @@ -1722,7 +1338,7 @@ public static function replaceGroups($mailingId, $type, $entity, $entityIds) { civicrm_api3('mailing_group', 'replace', [ 'mailing_id' => $mailingId, 'group_type' => $type, - 'entity_table' => ($entity == 'groups') ? CRM_Contact_BAO_Group::getTableName() : CRM_Mailing_BAO_Mailing::getTableName(), + 'entity_table' => ($entity === 'groups') ? CRM_Contact_BAO_Group::getTableName() : CRM_Mailing_BAO_Mailing::getTableName(), 'values' => $values, ]); } @@ -1898,10 +1514,10 @@ public static function &report($id, $skipDetails = FALSE, $isSMS = FALSE) { $row['name'] = "Search Results"; } - if ($mailing->group_type == 'Include') { + if ($mailing->group_type === 'Include') { $report['group']['include'][] = $row; } - elseif ($mailing->group_type == 'Base') { + elseif ($mailing->group_type === 'Base') { $report['group']['base'][] = $row; } else { @@ -2181,8 +1797,6 @@ public static function &report($id, $skipDetails = FALSE, $isSMS = FALSE) { public function getCount() { $this->selectAdd(); $this->selectAdd('COUNT(id) as count'); - - $session = CRM_Core_Session::singleton(); $this->find(TRUE); return $this->count; @@ -2317,9 +1931,6 @@ public static function mailingACLIDs() { public function &getRows($offset, $rowCount, $sort, $additionalClause = NULL, $additionalParams = NULL) { $mailing = self::getTableName(); $job = CRM_Mailing_BAO_MailingJob::getTableName(); - $group = CRM_Mailing_DAO_MailingGroup::getTableName(); - $session = CRM_Core_Session::singleton(); - $mailingACL = self::mailingACL(); //get all campaigns. @@ -2384,7 +1995,7 @@ public function &getRows($offset, $rowCount, $sort, $additionalClause = NULL, $a $rows[] = [ 'id' => $dao->id, 'name' => $dao->name, - 'status' => $dao->status ? $dao->status : 'Not scheduled', + 'status' => $dao->status ?: 'Not scheduled', 'created_date' => CRM_Utils_Date::customFormat($dao->created_date), 'scheduled' => CRM_Utils_Date::customFormat($dao->scheduled_date), 'scheduled_iso' => $dao->scheduled_date, @@ -2459,25 +2070,6 @@ public static function self_hook_civicrm_pre(\Civi\Core\Event\PreEvent $event) { } } - /** - * @deprecated - * Use CRM_Mailing_BAO_MailingJob::del($id) - * - * @param int $id - * Id of the Job to delete. - * - * @return void - */ - public static function delJob($id) { - if (empty($id)) { - throw new CRM_Core_Exception(ts('No id passed to mailing delJob function')); - } - - CRM_Core_Error::deprecatedWarning('This function is deprecated, use CRM_Mailing_BAO_MailingJob::del instead'); - - CRM_Mailing_BAO_MailingJob::deleteRecord(['id' => $id]); - } - /** * @deprecated * This is used by CiviMail but will be made redundant by FlexMailer/TokenProcessor. @@ -2485,7 +2077,7 @@ public static function delJob($id) { */ public function getReturnProperties() { $tokens = &$this->getTokens(); - + CRM_Core_Error::deprecatedWarning('function no longer called - use flexmailer'); $properties = []; if (isset($tokens['html']) && isset($tokens['html']['contact']) @@ -2595,9 +2187,11 @@ public static function commonCompose(&$form) { ['onChange' => "selectValue( this.value, '{$prefix}');", 'class' => 'crm-select2 huge'] ); } - $form->add('checkbox', "{$prefix}updateTemplate", ts('Update Template'), NULL); - $form->add('checkbox', "{$prefix}saveTemplate", ts('Save As New Template'), ['onclick' => "showSaveDetails(this, '{$prefix}');"]); - $form->add('text', "{$prefix}saveTemplateName", ts('Template Title')); + if (\CRM_Core_Permission::check('edit message templates')) { + $form->add('checkbox', "{$prefix}updateTemplate", ts('Update Template'), NULL); + $form->add('checkbox', "{$prefix}saveTemplate", ts('Save As New Template'), ['onclick' => "showSaveDetails(this, '{$prefix}');"]); + $form->add('text', "{$prefix}saveTemplateName", ts('Template Title')); + } } // I'm not sure this is ever called. @@ -2649,27 +2243,6 @@ public function searchMailingIDs() { * array content/component. */ public static function getMailingContent(&$report, &$form, $isSMS = FALSE) { - $htmlHeader = $textHeader = NULL; - $htmlFooter = $textFooter = NULL; - - if (!$isSMS) { - if ($report['mailing']['header_id']) { - $header = new CRM_Mailing_BAO_MailingComponent(); - $header->id = $report['mailing']['header_id']; - $header->find(TRUE); - $htmlHeader = $header->body_html; - $textHeader = $header->body_text; - } - - if ($report['mailing']['footer_id']) { - $footer = new CRM_Mailing_BAO_MailingComponent(); - $footer->id = $report['mailing']['footer_id']; - $footer->find(TRUE); - $htmlFooter = $footer->body_html; - $textFooter = $footer->body_text; - } - } - $mailingKey = $form->_mailing_id; if (!$isSMS) { if ($hash = CRM_Mailing_BAO_Mailing::getMailingHash($mailingKey)) { @@ -2872,10 +2445,10 @@ public static function getContactMailingSelector(&$params) { "mid={$values['mailing_id']}&reset=1&cid={$params['contact_id']}&event=queue&context=mailing"); $mailing['start_date'] = CRM_Utils_Date::customFormat($values['start_date']); //CRM-12814 - $mailing['openstats'] = "Opens: " . - CRM_Utils_Array::value($values['mailing_id'], $openCounts, 0) . - "
Clicks: " . - $clickCounts[$values['mailing_id']] ?? 0; + $clicks = $clickCounts[$values['mailing_id']] ?? 0; + $opens = $openCounts[$values['mailing_id']] ?? 0; + $mailing['openstats'] = ts('Opens: %1', [1 => $opens]) . '
' . + ts('Clicks: %1', [1 => $clicks]); $actionLinks = [ CRM_Core_Action::VIEW => [ diff --git a/www/modules/civicrm/CRM/Mailing/BAO/MailingJob.php b/www/modules/civicrm/CRM/Mailing/BAO/MailingJob.php index 326da1cb8..07ba6ea50 100644 --- a/www/modules/civicrm/CRM/Mailing/BAO/MailingJob.php +++ b/www/modules/civicrm/CRM/Mailing/BAO/MailingJob.php @@ -9,6 +9,7 @@ +--------------------------------------------------------------------+ */ use Civi\Api4\ActivityContact; +use Civi\Api4\MailingJob; /** * @@ -37,23 +38,18 @@ class CRM_Mailing_BAO_MailingJob extends CRM_Mailing_DAO_MailingJob { * * @param array $params * + * @deprecated since 5.71 will be removed around 5.85 + * * @return \CRM_Mailing_BAO_MailingJob * @throws \CRM_Core_Exception */ - public static function create($params) { - if (empty($params['id']) && empty($params['mailing_id'])) { - throw new CRM_Core_Exception("Failed to create job: Unknown mailing ID"); - } - $op = empty($params['id']) ? 'create' : 'edit'; - CRM_Utils_Hook::pre($op, 'MailingJob', $params['id'] ?? NULL, $params); - - $jobDAO = new CRM_Mailing_BAO_MailingJob(); - $jobDAO->copyValues($params); - $jobDAO->save(); + public static function create(array $params): self { + CRM_Core_Error::deprecatedWarning('use the api'); + $jobDAO = self::writeRecord($params); if (!empty($params['mailing_id']) && empty('is_calling_function_updated_to_reflect_deprecation')) { + CRM_Core_Error::deprecatedWarning('mail recipients should not be generated during MailingJob::create'); CRM_Mailing_BAO_Mailing::getRecipients($params['mailing_id']); } - CRM_Utils_Hook::post($op, 'MailingJob', $jobDAO->id, $jobDAO); return $jobDAO; } @@ -69,14 +65,12 @@ public static function create($params) { public static function runJobs($testParams = NULL, $mode = NULL) { $job = $mode === 'sms' ? new CRM_Mailing_BAO_SMSJob() : new CRM_Mailing_BAO_MailingJob(); - $jobTable = CRM_Mailing_DAO_MailingJob::getTableName(); - $mailingTable = CRM_Mailing_DAO_Mailing::getTableName(); $mailerBatchLimit = Civi::settings()->get('mailerBatchLimit'); if (!empty($testParams)) { $query = " SELECT * - FROM $jobTable + FROM civicrm_mailing_job WHERE id = {$testParams['job_id']}"; $job->query($query); } @@ -85,17 +79,14 @@ public static function runJobs($testParams = NULL, $mode = NULL) { $mailingACL = CRM_Mailing_BAO_Mailing::mailingACL('m'); $domainID = CRM_Core_Config::domainID(); - $modeClause = 'AND m.sms_provider_id IS NULL'; - if ($mode == 'sms') { - $modeClause = 'AND m.sms_provider_id IS NOT NULL'; - } + $modeClause = 'AND m.sms_provider_id ' . ($mode === 'sms' ? 'IS NOT NULL' : 'IS NULL'); // Select the first child job that is scheduled // CRM-6835 $query = " SELECT j.* - FROM $jobTable j, - $mailingTable m + FROM civicrm_mailing_job j, + civicrm_mailing m WHERE m.id = j.mailing_id AND m.domain_id = {$domainID} {$modeClause} AND j.is_test = 0 @@ -114,6 +105,7 @@ public static function runJobs($testParams = NULL, $mode = NULL) { } while ($job->fetch()) { + $mailingID = $job->mailing_id; // still use job level lock for each child job $lock = Civi::lockManager()->acquire("data.mailing.job.{$job->id}"); if (!$lock->isAcquired()) { @@ -134,8 +126,8 @@ public static function runJobs($testParams = NULL, $mode = NULL) { ); if ( - $job->status != 'Running' && - $job->status != 'Scheduled' + $job->status !== 'Running' && + $job->status !== 'Scheduled' ) { // this includes Cancelled and other statuses, CRM-4246 $lock->release(); @@ -145,37 +137,35 @@ public static function runJobs($testParams = NULL, $mode = NULL) { /* Queue up recipients for the child job being launched */ - if ($job->status != 'Running') { + if ($job->status !== 'Running') { $transaction = new CRM_Core_Transaction(); // have to queue it up based on the offset and limits // get the parent ID, and limit and offset - $job->queue($testParams); + if (!empty($testParams)) { + CRM_Mailing_BAO_Mailing::getTestRecipients($testParams, (int) $mailingID); + } + else { + self::queue((int) $job->mailing_id, (int) $job->job_offset, (int) $job->job_limit, (int) $job->id); + } // Update to show job has started. - self::create([ + MailingJob::update(FALSE)->setValues([ 'id' => $job->id, 'start_date' => date('YmdHis'), 'status' => 'Running', - ]); + ])->execute(); $transaction->commit(); } - // Get the mailer + // Compose and deliver each child job if ($mode === NULL) { $mailer = \Civi::service('pear_mail'); - } - elseif ($mode == 'sms') { - $mailer = CRM_SMS_Provider::singleton(['mailing_id' => $job->mailing_id]); - } - - // Compose and deliver each child job - if (\CRM_Utils_Constant::value('CIVICRM_FLEXMAILER_HACK_DELIVER')) { $isComplete = Civi\Core\Resolver::singleton()->call(CIVICRM_FLEXMAILER_HACK_DELIVER, [$job, $mailer, $testParams]); } - else { - $isComplete = $job->deliver($mailer, $testParams); + elseif ($mode === 'sms') { + $isComplete = $job->deliver(NULL, !empty($testParams)); } CRM_Utils_Hook::post('create', 'CRM_Mailing_DAO_Spool', $job->id, $isComplete); @@ -184,9 +174,11 @@ public static function runJobs($testParams = NULL, $mode = NULL) { if ($isComplete) { // Finish the job. - $transaction = new CRM_Core_Transaction(); - self::create(['id' => $job->id, 'end_date' => date('YmdHis'), 'status' => 'Complete']); - $transaction->commit(); + MailingJob::update(FALSE)->setValues([ + 'id' => $job->id, + 'end_date' => 'now', + 'status' => 'Complete', + ])->execute(); // don't mark the mailing as complete } @@ -350,7 +342,11 @@ public static function runJobs_pre(int $offset = 200, $mode = NULL): void { self::split_job((int) $offset, (int) $job->id, (int) $job->mailing_id, $job->scheduled_date); // Update the status of the parent job - self::create(['id' => $job->id, 'start_date' => date('YmdHis'), 'status' => 'Running']); + MailingJob::update(FALSE)->setValues([ + 'id' => $job->id, + 'start_date' => 'now', + 'status' => 'Running', + ])->execute(); $transaction->commit(); // Release the job lock @@ -407,347 +403,63 @@ private static function split_job(int $offset, int $jobID, int $mailingID, strin } /** - * @param ?array $testParams - */ - public function queue(?array $testParams = NULL) { - if (!empty($testParams)) { - CRM_Mailing_BAO_Mailing::getTestRecipients($testParams, (int) $this->mailing_id); - } - else { - // We are still getting all the recipients from the parent job - // so we don't mess with the include/exclude logic. - $recipients = CRM_Mailing_BAO_MailingRecipients::mailingQuery($this->mailing_id, $this->job_offset, $this->job_limit); - - $params = []; - $count = 0; - // dev/core#1768 Get the mail sync interval. - $mail_sync_interval = Civi::settings()->get('civimail_sync_interval'); - while ($recipients->fetch()) { - // CRM-18543: there are situations when both the email and phone are null. - // Skip the recipient in this case. - if (empty($recipients->email_id) && empty($recipients->phone_id)) { - continue; - } - $params[] = [ - 'job_id' => $this->id, - 'email_id' => $recipients->email_id ? (int) $recipients->email_id : NULL, - 'phone_id' => $recipients->phone_id ? (int) $recipients->phone_id : NULL, - 'contact_id' => $recipients->contact_id ? (int) $recipients->contact_id : NULL, - 'mailing_id' => (int) $this->mailing_id, - 'is_test' => !empty($testParams), - ]; - $count++; - /* - The mail sync interval is used here to determine how - many rows to insert in each insert statement. - The discussion & name of the setting implies that the intent of the - setting is the frequency with which the mailing tables are updated - with information about actions taken on the mailings (ie if you send - an email & quickly update the delivered table that impacts information - availability. - - However, here it is used to manage the size of each individual - insert statement. It is unclear why as the trade offs are out of sync - ie. you want you insert statements here to be 'big, but not so big they - stall out' but in the delivery context it's a trade off between - information availability & performance. - https://github.com/civicrm/civicrm-core/pull/17367 */ - - if ($count % $mail_sync_interval === 0) { - CRM_Mailing_Event_BAO_MailingEventQueue::writeRecords($params); - $count = 0; - $params = []; - } - } - - if (!empty($params)) { - CRM_Mailing_Event_BAO_MailingEventQueue::writeRecords($params); - } - } - } - - /** - * Send the mailing. - * - * @deprecated - * This is used by CiviMail but will be made redundant by FlexMailer. - * @param object $mailer - * A Mail object to send the messages. - * - * @param array $testParams - * @return bool - */ - public function deliver(&$mailer, $testParams = NULL) { - if (\Civi::settings()->get('experimentalFlexMailerEngine')) { - throw new \RuntimeException("Cannot use legacy deliver() when experimentalFlexMailerEngine is enabled"); - } - - $mailing = new CRM_Mailing_BAO_Mailing(); - $mailing->id = $this->mailing_id; - $mailing->find(TRUE); - - $config = NULL; - - if ($config == NULL) { - $config = CRM_Core_Config::singleton(); - } - - if (property_exists($mailing, 'language') && $mailing->language && $mailing->language != CRM_Core_I18n::getLocale()) { - $swapLang = CRM_Utils_AutoClean::swap('global://dbLocale?getter', 'call://i18n/setLocale', $mailing->language); - } - - $job_date = $this->scheduled_date; - $fields = []; - - if (!empty($testParams)) { - $mailing->subject = ts('[CiviMail Draft]') . ' ' . $mailing->subject; - } - - CRM_Mailing_BAO_Mailing::tokenReplace($mailing); - - // get and format attachments - $attachments = CRM_Core_BAO_File::getEntityFile('civicrm_mailing', $mailing->id); - - if (defined('CIVICRM_MAIL_SMARTY') && CIVICRM_MAIL_SMARTY) { - CRM_Core_Smarty::registerStringResource(); - } - - // CRM-12376 - // This handles the edge case scenario where all the mails - // have been delivered in prior jobs. - $isDelivered = TRUE; - - // make sure that there's no more than $mailerBatchLimit mails processed in a run - $mailerBatchLimit = Civi::settings()->get('mailerBatchLimit'); - $eq = self::findPendingTasks($this->id, $mailing->sms_provider_id ? 'sms' : 'email'); - while ($eq->fetch()) { - if ($mailerBatchLimit > 0 && self::$mailsProcessed >= $mailerBatchLimit) { - if (!empty($fields)) { - $this->deliverGroup($fields, $mailing, $mailer, $job_date, $attachments); - } - return FALSE; - } - self::$mailsProcessed++; - - $fields[] = [ - 'id' => $eq->id, - 'hash' => $eq->hash, - 'contact_id' => $eq->contact_id, - 'email' => $eq->email, - 'phone' => $eq->phone, - ]; - if (count($fields) == self::MAX_CONTACTS_TO_PROCESS) { - $isDelivered = $this->deliverGroup($fields, $mailing, $mailer, $job_date, $attachments); - if (!$isDelivered) { - return $isDelivered; - } - $fields = []; - } - } - - if (!empty($fields)) { - $isDelivered = $this->deliverGroup($fields, $mailing, $mailer, $job_date, $attachments); - } - return $isDelivered; - } - - /** - * @deprecated - * This is used by CiviMail but will be made redundant by FlexMailer. - * @param array $fields - * List of intended recipients. - * Each recipient is an array with keys 'hash', 'contact_id', 'email', etc. - * @param $mailing - * @param $mailer - * @param $job_date - * @param $attachments + * @param int $mailingID + * @param int $jobOffset + * @param int $limit + * @param int $jobID * - * @return bool|null - * @throws Exception + * @return void */ - public function deliverGroup(&$fields, &$mailing, &$mailer, &$job_date, &$attachments) { - static $smtpConnectionErrors = 0; - - if (!is_object($mailer) || empty($fields)) { - throw new CRM_Core_Exception('Either mailer is not an object or we don\'t have recipients to send to in this group'); - } + private static function queue(int $mailingID, int $jobOffset, int $limit, int $jobID): void { + // We are still getting all the recipients from the parent job + // so we don't mess with the include/exclude logic. + $recipients = CRM_Mailing_BAO_MailingRecipients::mailingQuery($mailingID, $jobOffset, $limit); - // get the return properties - $returnProperties = $mailing->getReturnProperties(); - $params = $targetParams = $deliveredParams = []; + $params = []; $count = 0; // dev/core#1768 Get the mail sync interval. $mail_sync_interval = Civi::settings()->get('civimail_sync_interval'); - $retryGroup = FALSE; - - foreach ($fields as $key => $field) { - $params[] = $field['contact_id']; - } - - [$details] = CRM_Utils_Token::getTokenDetails( - $params, - $returnProperties, - TRUE, TRUE, NULL, - $mailing->getFlattenedTokens(), - get_class($this), - $this->id - ); - - foreach ($fields as $key => $field) { - $contactID = $field['contact_id']; - if (!array_key_exists($contactID, $details)) { - $details[$contactID] = []; - } - - // Compose the mailing. - $recipient = $replyToEmail = NULL; - $replyValue = strcmp($mailing->replyto_email, $mailing->from_email); - if ($replyValue) { - $replyToEmail = $mailing->replyto_email; - } - - $message = $mailing->compose( - $this->id, $field['id'], $field['hash'], - $field['contact_id'], $field['email'], - $recipient, FALSE, $details[$contactID], $attachments, - FALSE, NULL, $replyToEmail - ); - if (empty($message)) { - // lets keep the message in the queue - // most likely a permissions related issue with smarty templates - // or a bad contact id? CRM-9833 + while ($recipients->fetch()) { + // CRM-18543: there are situations when both the email and phone are null. + // Skip the recipient in this case. + if (empty($recipients->email_id) && empty($recipients->phone_id)) { continue; } - - // Send the mailing. - - $body = $message->get(); - $headers = $message->headers(); - - // make $recipient actually be the *encoded* header, so as not to baffle Mail_RFC822, CRM-5743 - $recipient = $headers['To']; - $result = NULL; - - // disable error reporting on real mailings (but leave error reporting for tests), CRM-5744 - if ($job_date) { - $errorScope = CRM_Core_TemporaryErrorScope::ignoreException(); - } - - $result = $mailer->send($recipient, $headers, $body, $this->id); - - if ($job_date) { - unset($errorScope); - } - - if (is_a($result, 'PEAR_Error') && !$mailing->sms_provider_id) { - // CRM-9191 - $message = $result->getMessage(); - if ($this->isTemporaryError($message)) { - // lets log this message and code - $code = $result->getCode(); - CRM_Core_Error::debug_log_message("SMTP Socket Error or failed to set sender error. Message: $message, Code: $code"); - - // these are socket write errors which most likely means smtp connection errors - // lets skip them and reconnect. - $smtpConnectionErrors++; - if ($smtpConnectionErrors <= 5) { - $mailer->disconnect(); - $retryGroup = TRUE; - continue; - } - - // seems like we have too many of them in a row, we should - // write stuff to disk and abort the cron job - $this->writeToDB( - $deliveredParams, - $targetParams, - $mailing, - $job_date - ); - - CRM_Core_Error::debug_log_message("Too many SMTP Socket Errors. Exiting"); - CRM_Utils_System::civiExit(); - } - - // Register the bounce event. - - $params = [ - 'event_queue_id' => $field['id'], - 'job_id' => $this->id, - 'hash' => $field['hash'], - ]; - $params = array_merge($params, - CRM_Mailing_BAO_BouncePattern::match($result->getMessage()) - ); - CRM_Mailing_Event_BAO_MailingEventBounce::recordBounce($params); - } - elseif (is_a($result, 'PEAR_Error') && $mailing->sms_provider_id) { - // Handle SMS errors: CRM-15426 - $job_id = intval($this->id); - $mailing_id = intval($mailing->id); - CRM_Core_Error::debug_log_message("Failed to send SMS message. Vars: mailing_id: {$mailing_id}, job_id: {$job_id}. Error message follows."); - CRM_Core_Error::debug_log_message($result->getMessage()); - } - else { - // Register the delivery event. - $deliveredParams[] = $field['id']; - $targetParams[] = $field['contact_id']; - - $count++; - // dev/core#1768 Mail sync interval is now configurable. - if ($count % $mail_sync_interval == 0) { - $this->writeToDB( - $deliveredParams, - $targetParams, - $mailing, - $job_date - ); - $count = 0; - - // hack to stop mailing job at run time, CRM-4246. - // to avoid making too many DB calls for this rare case - // lets do it when we snapshot - $status = CRM_Core_DAO::getFieldValue( - 'CRM_Mailing_DAO_MailingJob', - $this->id, - 'status', - 'id', - TRUE - ); - - if ($status != 'Running') { - return FALSE; - } - } - } - - unset($result); - - // seems like a successful delivery or bounce, lets decrement error count - // only if we have smtp connection errors - if ($smtpConnectionErrors > 0) { - $smtpConnectionErrors--; - } - - // If we have enabled the Throttle option, this is the time to enforce it. - $mailThrottleTime = Civi::settings()->get('mailThrottleTime'); - if (!empty($mailThrottleTime)) { - usleep((int ) $mailThrottleTime); + $params[] = [ + 'job_id' => $jobID, + 'email_id' => $recipients->email_id ? (int) $recipients->email_id : NULL, + 'phone_id' => $recipients->phone_id ? (int) $recipients->phone_id : NULL, + 'contact_id' => $recipients->contact_id ? (int) $recipients->contact_id : NULL, + 'mailing_id' => (int) $mailingID, + 'is_test' => FALSE, + ]; + $count++; + /* + The mail sync interval is used here to determine how + many rows to insert in each insert statement. + The discussion & name of the setting implies that the intent of the + setting is the frequency with which the mailing tables are updated + with information about actions taken on the mailings (ie if you send + an email & quickly update the delivered table that impacts information + availability. + + However, here it is used to manage the size of each individual + insert statement. It is unclear why as the trade offs are out of sync + ie. you want you insert statements here to be 'big, but not so big they + stall out' but in the delivery context it's a trade off between + information availability & performance. + https://github.com/civicrm/civicrm-core/pull/17367 */ + + if ($count % $mail_sync_interval === 0) { + CRM_Mailing_Event_BAO_MailingEventQueue::writeRecords($params); + $count = 0; + $params = []; } } - $result = $this->writeToDB( - $deliveredParams, - $targetParams, - $mailing, - $job_date - ); - - if ($retryGroup) { - return FALSE; + if (!empty($params)) { + CRM_Mailing_Event_BAO_MailingEventQueue::writeRecords($params); } - - return $result; } /** @@ -894,7 +606,7 @@ public static function status($status) { 'Canceled' => ts('Canceled'), ]; } - return CRM_Utils_Array::value($status, $translation, ts('Not scheduled')); + return $translation[$status] ?? ts('Not scheduled'); } /** @@ -1030,70 +742,6 @@ public function writeToDB( return $result; } - /** - * Search the mailing-event queue for a list of pending delivery tasks. - * - * @param int $jobId - * @param string $medium - * Ex: 'email' or 'sms'. - * - * @return \CRM_Mailing_Event_BAO_MailingEventQueue - * A query object whose rows provide ('id', 'contact_id', 'hash') and ('email' or 'phone'). - */ - public static function findPendingTasks($jobId, $medium) { - $eq = new CRM_Mailing_Event_BAO_MailingEventQueue(); - $queueTable = CRM_Mailing_Event_BAO_MailingEventQueue::getTableName(); - $emailTable = CRM_Core_BAO_Email::getTableName(); - $phoneTable = CRM_Core_BAO_Phone::getTableName(); - $contactTable = CRM_Contact_BAO_Contact::getTableName(); - $deliveredTable = CRM_Mailing_Event_BAO_MailingEventDelivered::getTableName(); - $bounceTable = CRM_Mailing_Event_BAO_MailingEventBounce::getTableName(); - - $query = " SELECT $queueTable.id, - $emailTable.email as email, - $queueTable.contact_id, - $queueTable.hash, - NULL as phone - FROM $queueTable - INNER JOIN $emailTable - ON $queueTable.email_id = $emailTable.id - INNER JOIN $contactTable - ON $contactTable.id = $emailTable.contact_id - LEFT JOIN $deliveredTable - ON $queueTable.id = $deliveredTable.event_queue_id - LEFT JOIN $bounceTable - ON $queueTable.id = $bounceTable.event_queue_id - WHERE $queueTable.job_id = " . $jobId . " - AND $deliveredTable.id IS null - AND $bounceTable.id IS null - AND $contactTable.is_opt_out = 0"; - - if ($medium === 'sms') { - $query = " - SELECT $queueTable.id, - $phoneTable.phone as phone, - $queueTable.contact_id, - $queueTable.hash, - NULL as email - FROM $queueTable - INNER JOIN $phoneTable - ON $queueTable.phone_id = $phoneTable.id - INNER JOIN $contactTable - ON $contactTable.id = $phoneTable.contact_id - LEFT JOIN $deliveredTable - ON $queueTable.id = $deliveredTable.event_queue_id - LEFT JOIN $bounceTable - ON $queueTable.id = $bounceTable.event_queue_id - WHERE $queueTable.job_id = " . $jobId . " - AND $deliveredTable.id IS null - AND $bounceTable.id IS null - AND ( $contactTable.is_opt_out = 0 - OR $contactTable.do_not_sms = 0 )"; - } - $eq->query($query); - return $eq; - } - /** * Delete the mailing job. * diff --git a/www/modules/civicrm/CRM/Mailing/BAO/MailingTrackableURL.php b/www/modules/civicrm/CRM/Mailing/BAO/MailingTrackableURL.php index 2ebf1faa8..d2b24902e 100644 --- a/www/modules/civicrm/CRM/Mailing/BAO/MailingTrackableURL.php +++ b/www/modules/civicrm/CRM/Mailing/BAO/MailingTrackableURL.php @@ -141,7 +141,7 @@ private static function getTrackerURLForUrlWithTokens($url, $mailing_id, $queue_ // Append the tokenised bits and the fragment. if ($tokenised_params) { // We know the URL will already have the '?' - $data .= '&' . implode('&', $tokenised_params); + $data .= (str_contains($data, '?') ? '&' : '?') . implode('&', $tokenised_params); } if (!empty($parsed[3])) { $data .= $parsed[3]; diff --git a/www/modules/civicrm/CRM/Mailing/BAO/SMSJob.php b/www/modules/civicrm/CRM/Mailing/BAO/SMSJob.php index 1f25c1193..cf2733c99 100644 --- a/www/modules/civicrm/CRM/Mailing/BAO/SMSJob.php +++ b/www/modules/civicrm/CRM/Mailing/BAO/SMSJob.php @@ -6,19 +6,139 @@ class CRM_Mailing_BAO_SMSJob extends CRM_Mailing_BAO_MailingJob { /** - * This is used by CiviMail but will be made redundant by FlexMailer. + * Send the mailing. + * + * @param null $unused + * @param bool $isTest + * + * @return bool + * @throws \CRM_Core_Exception + * @internal + * + */ + public function deliver($unused, $isTest) { + // Just in case flexmailer is passing something odd. + $isTest = !empty($isTest); + $mailingID = $this->mailing_id; + $mailer = CRM_SMS_Provider::singleton(['mailing_id' => $mailingID]); + + $mailing = new CRM_Mailing_BAO_Mailing(); + $mailing->id = $this->mailing_id; + $mailing->find(TRUE); + + $config = NULL; + + if ($config == NULL) { + $config = CRM_Core_Config::singleton(); + } + + if (property_exists($mailing, 'language') && $mailing->language && $mailing->language != CRM_Core_I18n::getLocale()) { + $swapLang = CRM_Utils_AutoClean::swap('global://dbLocale?getter', 'call://i18n/setLocale', $mailing->language); + } + + $job_date = $this->scheduled_date; + $fields = []; + + if ($isTest) { + $mailing->subject = ts('[CiviMail Draft]') . ' ' . $mailing->subject; + } + + if (defined('CIVICRM_MAIL_SMARTY') && CIVICRM_MAIL_SMARTY) { + // This is probably a hang over from when Civi was not probably initialised. + // It is not relevant once we are off Smarty 2. + CRM_Core_Smarty::registerStringResource(); + } + + // CRM-12376 + // This handles the edge case scenario where all the mails + // have been delivered in prior jobs. + $isDelivered = TRUE; + + // make sure that there's no more than $mailerBatchLimit mails processed in a run + $mailerBatchLimit = Civi::settings()->get('mailerBatchLimit'); + $eq = self::findPendingSMSTasks((int) $this->id); + while ($eq->fetch()) { + if ($mailerBatchLimit > 0 && self::$mailsProcessed >= $mailerBatchLimit) { + if (!empty($fields)) { + $this->deliverGroup($fields, $mailing, $mailer, $job_date); + } + return FALSE; + } + self::$mailsProcessed++; + + $fields[] = [ + 'id' => $eq->id, + 'hash' => $eq->hash, + 'contact_id' => $eq->contact_id, + 'email' => $eq->email, + 'phone' => $eq->phone, + ]; + if (count($fields) == self::MAX_CONTACTS_TO_PROCESS) { + $isDelivered = $this->deliverGroup($fields, $mailing, $mailer, $job_date); + if (!$isDelivered) { + return $isDelivered; + } + $fields = []; + } + } + + if (!empty($fields)) { + $isDelivered = $this->deliverGroup($fields, $mailing, $mailer, $job_date); + } + return $isDelivered; + } + + /** + * Search the mailing-event queue for a list of pending delivery tasks. + * + * @param int $jobId + * + * @return \CRM_Mailing_Event_BAO_MailingEventQueue + * A query object whose rows provide ('id', 'contact_id', 'hash') and ('email' or 'phone'). + */ + private static function findPendingSMSTasks(int $jobId): CRM_Mailing_Event_BAO_MailingEventQueue { + $eq = new CRM_Mailing_Event_BAO_MailingEventQueue(); + $query = " + SELECT queue.id, + phone, + queue.contact_id, + queue.hash, + NULL as email + FROM civicrm_mailing_event_queue queue + INNER JOIN civicrm_phone phone + ON queue.phone_id = phone.id + INNER JOIN civicrm_contact contact + ON contact.id = phone.contact_id + LEFT JOIN civicrm_mailing_event_delivered delivered + ON queue.id = delivered.event_queue_id + LEFT JOIN civicrm_mailing_event_bounce bounce + ON queue.id = bounce.event_queue_id + WHERE queue.job_id = " . $jobId . " + AND delivered.id IS null + AND bounce.id IS null + AND ( contact.is_opt_out = 0 + OR contact.do_not_sms = 0 )"; + // note this `query` function leaks memory more than CRM_Core_DAO::ExecuteQuery() + $eq->query($query); + return $eq; + } + + /** + * This is used by to deliver SMS. + * + * @internal only to be used by core CiviCRM code. + * * @param array $fields * List of intended recipients. * Each recipient is an array with keys 'hash', 'contact_id', 'email', etc. * @param $mailing * @param $mailer * @param $job_date - * @param $attachments * * @return bool|null * @throws Exception */ - public function deliverGroup(&$fields, &$mailing, &$mailer, &$job_date, &$attachments) { + private function deliverGroup($fields, &$mailing, $mailer, $job_date) { $count = 0; // dev/core#1768 Get the mail sync interval. $mail_sync_interval = Civi::settings()->get('civimail_sync_interval'); @@ -38,7 +158,7 @@ public function deliverGroup(&$fields, &$mailing, &$mailer, &$job_date, &$attach ]; CRM_Utils_Hook::alterMailParams($mailParams, 'civimail'); $body = $mailParams['text']; - $headers = ['To' => $field['phone']]; + $headers = ['To' => $field['phone'], 'contact_id' => $contact['id']]; try { $result = $mailer->send($headers['To'], $headers, $body, $this->id); diff --git a/www/modules/civicrm/CRM/Mailing/DAO/BouncePattern.php b/www/modules/civicrm/CRM/Mailing/DAO/BouncePattern.php index ed56a5d5d..56c0f0d35 100644 --- a/www/modules/civicrm/CRM/Mailing/DAO/BouncePattern.php +++ b/www/modules/civicrm/CRM/Mailing/DAO/BouncePattern.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Mailing/BouncePattern.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:cb74ea782c1326413de34b89cab91124) + * (GenCodeChecksum:08e977099cd44998287a6ad5e90de6c8) */ /** diff --git a/www/modules/civicrm/CRM/Mailing/DAO/BounceType.php b/www/modules/civicrm/CRM/Mailing/DAO/BounceType.php index 89ef58bd0..4885313fd 100644 --- a/www/modules/civicrm/CRM/Mailing/DAO/BounceType.php +++ b/www/modules/civicrm/CRM/Mailing/DAO/BounceType.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Mailing/BounceType.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:54fc337778d1208a78ef9eac006ce1b5) + * (GenCodeChecksum:c43d2f642e0fa4858c874fbd1f8e04c4) */ /** diff --git a/www/modules/civicrm/CRM/Mailing/DAO/Mailing.php b/www/modules/civicrm/CRM/Mailing/DAO/Mailing.php index 1ec69fe84..a28591a28 100644 --- a/www/modules/civicrm/CRM/Mailing/DAO/Mailing.php +++ b/www/modules/civicrm/CRM/Mailing/DAO/Mailing.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Mailing/Mailing.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:919b743c8a46a78f155d417cc1bd0631) + * (GenCodeChecksum:38a986a63d53ba6681bfa9f6be7181b5) */ /** @@ -1398,7 +1398,7 @@ public static function &fields() { 'entity' => 'Mailing', 'bao' => 'CRM_Mailing_BAO_Mailing', 'localizable' => 0, - 'FKClassName' => 'CRM_SMS_DAO_Provider', + 'FKClassName' => 'CRM_SMS_DAO_SmsProvider', 'FKColumnName' => 'id', 'html' => [ 'type' => 'Select', diff --git a/www/modules/civicrm/CRM/Mailing/DAO/MailingAB.php b/www/modules/civicrm/CRM/Mailing/DAO/MailingAB.php index 46e575e70..090e223cf 100644 --- a/www/modules/civicrm/CRM/Mailing/DAO/MailingAB.php +++ b/www/modules/civicrm/CRM/Mailing/DAO/MailingAB.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Mailing/MailingAB.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:bd7f5528773566cb04becedf849c5e1f) + * (GenCodeChecksum:b533666589df033383322752aae451ed) */ /** diff --git a/www/modules/civicrm/CRM/Mailing/DAO/MailingComponent.php b/www/modules/civicrm/CRM/Mailing/DAO/MailingComponent.php index 3b396d36f..aacd172cf 100644 --- a/www/modules/civicrm/CRM/Mailing/DAO/MailingComponent.php +++ b/www/modules/civicrm/CRM/Mailing/DAO/MailingComponent.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Mailing/MailingComponent.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:7e8514edf9fb7b55783cc813df572660) + * (GenCodeChecksum:4acc5bcb4973b8f5879c7a78cdd9ff7e) */ /** diff --git a/www/modules/civicrm/CRM/Mailing/DAO/MailingGroup.php b/www/modules/civicrm/CRM/Mailing/DAO/MailingGroup.php index a65cca122..0c95cc5d9 100644 --- a/www/modules/civicrm/CRM/Mailing/DAO/MailingGroup.php +++ b/www/modules/civicrm/CRM/Mailing/DAO/MailingGroup.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Mailing/MailingGroup.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:65ad1f210e2b4be6f4608e72694619a3) + * (GenCodeChecksum:b0c639058b4099bf4ce28edf8c1aa73b) */ /** diff --git a/www/modules/civicrm/CRM/Mailing/DAO/MailingJob.php b/www/modules/civicrm/CRM/Mailing/DAO/MailingJob.php index da849dd5e..8a6b14384 100644 --- a/www/modules/civicrm/CRM/Mailing/DAO/MailingJob.php +++ b/www/modules/civicrm/CRM/Mailing/DAO/MailingJob.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Mailing/MailingJob.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:61340ad6fba42d576e6289778b494bcc) + * (GenCodeChecksum:aea716406932dbefe1d93e1ddefa67ef) */ /** diff --git a/www/modules/civicrm/CRM/Mailing/DAO/MailingRecipients.php b/www/modules/civicrm/CRM/Mailing/DAO/MailingRecipients.php index f74938af6..7daa8b82d 100644 --- a/www/modules/civicrm/CRM/Mailing/DAO/MailingRecipients.php +++ b/www/modules/civicrm/CRM/Mailing/DAO/MailingRecipients.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Mailing/MailingRecipients.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:bf17f03f0d47a6eda3deab64a581aec8) + * (GenCodeChecksum:525e0d06e1197eb1265f0dc16dc27f90) */ /** diff --git a/www/modules/civicrm/CRM/Mailing/DAO/MailingTrackableURL.php b/www/modules/civicrm/CRM/Mailing/DAO/MailingTrackableURL.php index 76ad4f4c1..7a7e3b76a 100644 --- a/www/modules/civicrm/CRM/Mailing/DAO/MailingTrackableURL.php +++ b/www/modules/civicrm/CRM/Mailing/DAO/MailingTrackableURL.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Mailing/MailingTrackableURL.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:ce6c77dd702379d639959d67e7980a78) + * (GenCodeChecksum:d67afb11bc3fcc7f504a6e1c1b224c97) */ /** diff --git a/www/modules/civicrm/CRM/Mailing/DAO/Spool.php b/www/modules/civicrm/CRM/Mailing/DAO/Spool.php index ba0cf1470..9129b3387 100644 --- a/www/modules/civicrm/CRM/Mailing/DAO/Spool.php +++ b/www/modules/civicrm/CRM/Mailing/DAO/Spool.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Mailing/Spool.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:7c22f5a9de4a49ac64bf394c58b8cd4c) + * (GenCodeChecksum:3428809b9b08d641d615c79f93caafb3) */ /** diff --git a/www/modules/civicrm/CRM/Mailing/Event/BAO/MailingEventForward.php b/www/modules/civicrm/CRM/Mailing/Event/BAO/MailingEventForward.php index 0b29b6a08..2b3ed3549 100644 --- a/www/modules/civicrm/CRM/Mailing/Event/BAO/MailingEventForward.php +++ b/www/modules/civicrm/CRM/Mailing/Event/BAO/MailingEventForward.php @@ -16,353 +16,6 @@ */ class CRM_Mailing_Event_BAO_MailingEventForward extends CRM_Mailing_Event_DAO_MailingEventForward { - /** - * Create a new forward event, create a new contact if necessary - * - * @param $job_id - * @param $queue_id - * @param $hash - * @param $forward_email - * @param string|null $fromEmail - * @param array|null $comment - * - * @return bool - */ - public static function &forward($job_id, $queue_id, $hash, $forward_email, $fromEmail = NULL, $comment = NULL) { - $q = CRM_Mailing_Event_BAO_MailingEventQueue::verify(NULL, $queue_id, $hash); - - $successfulForward = FALSE; - $contact_id = NULL; - if (!$q) { - return $successfulForward; - } - - // Find the email address/contact, if it exists. - - $contact = CRM_Contact_BAO_Contact::getTableName(); - $email = CRM_Core_BAO_Email::getTableName(); - $queueTable = CRM_Mailing_Event_BAO_MailingEventQueue::getTableName(); - $job = CRM_Mailing_BAO_MailingJob::getTableName(); - - $dao = new CRM_Core_DAO(); - $dao->query(" - SELECT $contact.id as contact_id, - $email.id as email_id, - $contact.do_not_email as do_not_email, - $queueTable.id as queue_id - FROM ($email, $job as temp_job) - INNER JOIN $contact - ON $email.contact_id = $contact.id - LEFT JOIN $queueTable - ON $email.id = $queueTable.email_id - LEFT JOIN $job - ON $queueTable.job_id = $job.id - AND temp_job.mailing_id = $job.mailing_id - WHERE $queueTable.job_id = $job_id - AND $email.email = '" . - CRM_Utils_Type::escape($forward_email, 'String') . "'" - ); - - $dao->fetch(); - - $transaction = new CRM_Core_Transaction(); - - if (isset($dao->queue_id) || - (isset($dao->do_not_email) && $dao->do_not_email == 1) - ) { - // We already sent this mailing to $forward_email, or we should - // never email this contact. Give up. - - return $successfulForward; - } - - require_once 'api/api.php'; - $contactParams = [ - 'email' => $forward_email, - 'version' => 3, - ]; - $contactValues = civicrm_api('contact', 'get', $contactParams); - $count = $contactValues['count']; - - if ($count == 0) { - // If the contact does not exist, create one. - - $formatted = [ - 'contact_type' => 'Individual', - 'version' => 3, - ]; - $locationType = CRM_Core_BAO_LocationType::getDefault(); - $value = [ - 'email' => $forward_email, - 'location_type_id' => $locationType->id, - ]; - self::_civicrm_api3_deprecated_add_formatted_param($value, $formatted); - $formatted['onDuplicate'] = CRM_Import_Parser::DUPLICATE_SKIP; - $formatted['fixAddress'] = TRUE; - $contact = civicrm_api('contact', 'create', $formatted); - if (civicrm_error($contact)) { - return $successfulForward; - } - $contact_id = $contact['id']; - } - $email = new CRM_Core_DAO_Email(); - $email->email = $forward_email; - $email->find(TRUE); - $email_id = $email->id; - if (!$contact_id) { - $contact_id = $email->contact_id; - } - - // Create a new queue event. - - $queue_params = [ - 'email_id' => $email_id, - 'contact_id' => $contact_id, - 'job_id' => $job_id, - ]; - - $queue = CRM_Mailing_Event_BAO_MailingEventQueue::create($queue_params); - - $forward = new CRM_Mailing_Event_BAO_MailingEventForward(); - $forward->time_stamp = date('YmdHis'); - $forward->event_queue_id = $queue_id; - $forward->dest_queue_id = $queue->id; - $forward->save(); - - $dao->reset(); - $dao->query(" SELECT $job.mailing_id as mailing_id - FROM $job - WHERE $job.id = " . - CRM_Utils_Type::escape($job_id, 'Integer') - ); - $dao->fetch(); - $mailing_obj = new CRM_Mailing_BAO_Mailing(); - $mailing_obj->id = $dao->mailing_id; - $mailing_obj->find(TRUE); - - $config = CRM_Core_Config::singleton(); - $mailer = \Civi::service('pear_mail'); - - $recipient = NULL; - $attachments = NULL; - $message = $mailing_obj->compose($job_id, $queue->id, $queue->hash, - $queue->contact_id, $forward_email, $recipient, FALSE, NULL, $attachments, TRUE, $fromEmail - ); - //append comment if added while forwarding. - if (count($comment)) { - $message->_txtbody = ($comment['body_text'] ?? '') . $message->_txtbody; - if (!empty($comment['body_html'])) { - $message->_htmlbody = $comment['body_html'] . '
---------------Original message---------------------
' . $message->_htmlbody; - } - } - - $body = $message->get(); - $headers = $message->headers(); - - $result = NULL; - if (is_object($mailer)) { - $errorScope = CRM_Core_TemporaryErrorScope::ignoreException(); - $result = $mailer->send($recipient, $headers, $body); - unset($errorScope); - } - - $params = [ - 'event_queue_id' => $queue->id, - 'job_id' => $job_id, - 'hash' => $queue->hash, - ]; - if (is_a($result, 'PEAR_Error')) { - // Register the bounce event. - - $params = array_merge($params, - CRM_Mailing_BAO_BouncePattern::match($result->getMessage()) - ); - CRM_Mailing_Event_BAO_MailingEventBounce::recordBounce($params); - } - else { - $successfulForward = TRUE; - // Register the delivery event. - - CRM_Mailing_Event_BAO_MailingEventDelivered::recordDelivery($params); - } - - $transaction->commit(); - - return $successfulForward; - } - - /** - * This function adds the contact variable in $values to the - * parameter list $params. For most cases, $values should have length 1. If - * the variable being added is a child of Location, a location_type_id must - * also be included. If it is a child of phone, a phone_type must be included. - * - * @param array $values - * The variable(s) to be added. - * @param array $params - * The structured parameter list. - * - * @return bool|CRM_Utils_Error - */ - protected static function _civicrm_api3_deprecated_add_formatted_param(&$values, &$params) { - // @todo - most of this code is UNREACHABLE. - // Crawl through the possible classes: - // Contact - // Individual - // Household - // Organization - // Location - // Address - // Email - // Phone - // IM - // Note - // Custom - - // Cache the various object fields - static $fields = NULL; - - if ($fields == NULL) { - $fields = []; - } - - // first add core contact values since for other Civi modules they are not added - require_once 'CRM/Contact/BAO/Contact.php'; - $contactFields = CRM_Contact_DAO_Contact::fields(); - _civicrm_api3_store_values($contactFields, $values, $params); - - // get the formatted location blocks into params - w/ 3.0 format, CRM-4605 - if (!empty($values['location_type_id'])) { - foreach (['Phone', 'Email', 'IM', 'OpenID', 'Phone_Ext'] as $block) { - $name = strtolower($block); - if (!array_key_exists($name, $values)) { - continue; - } - - if ($name === 'phone_ext') { - $block = 'Phone'; - } - - // block present in value array. - if (!array_key_exists($name, $params) || !is_array($params[$name])) { - $params[$name] = []; - } - - if (!array_key_exists($block, $fields)) { - $className = "CRM_Core_DAO_$block"; - $fields[$block] =& $className::fields(); - } - - $blockCnt = count($params[$name]); - - // copy value to dao field name. - if ($name == 'im') { - $values['name'] = $values[$name]; - } - - _civicrm_api3_store_values($fields[$block], $values, - $params[$name][++$blockCnt] - ); - - if (empty($params['id']) && ($blockCnt == 1)) { - $params[$name][$blockCnt]['is_primary'] = TRUE; - } - - // we only process single block at a time. - return TRUE; - } - - // handle address fields. - if (!array_key_exists('address', $params) || !is_array($params['address'])) { - $params['address'] = []; - } - - $addressCnt = 1; - foreach ($params['address'] as $cnt => $addressBlock) { - if (($values['location_type_id'] ?? NULL) == - CRM_Utils_Array::value('location_type_id', $addressBlock) - ) { - $addressCnt = $cnt; - break; - } - $addressCnt++; - } - - if (!array_key_exists('Address', $fields)) { - $fields['Address'] = CRM_Core_DAO_Address::fields(); - } - - // Note: we doing multiple value formatting here for address custom fields, plus putting into right format. - // The actual formatting (like date, country ..etc) for address custom fields is taken care of while saving - // the address in CRM_Core_BAO_Address::create method - if (!empty($values['location_type_id'])) { - static $customFields = []; - if (empty($customFields)) { - $customFields = CRM_Core_BAO_CustomField::getFields('Address'); - } - // make a copy of values, as we going to make changes - $newValues = $values; - foreach ($values as $key => $val) { - $customFieldID = CRM_Core_BAO_CustomField::getKeyID($key); - if ($customFieldID && array_key_exists($customFieldID, $customFields)) { - // mark an entry in fields array since we want the value of custom field to be copied - $fields['Address'][$key] = NULL; - - $htmlType = $customFields[$customFieldID]['html_type'] ?? NULL; - if (CRM_Core_BAO_CustomField::isSerialized($customFields[$customFieldID]) && $val) { - $mulValues = explode(',', $val); - $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE); - $newValues[$key] = []; - foreach ($mulValues as $v1) { - foreach ($customOption as $v2) { - if ((strtolower($v2['label']) == strtolower(trim($v1))) || - (strtolower($v2['value']) == strtolower(trim($v1))) - ) { - if ($htmlType == 'CheckBox') { - $newValues[$key][$v2['value']] = 1; - } - else { - $newValues[$key][] = $v2['value']; - } - } - } - } - } - } - } - // consider new values - $values = $newValues; - } - - _civicrm_api3_store_values($fields['Address'], $values, $params['address'][$addressCnt]); - - $addressFields = [ - 'county', - 'country', - 'state_province', - 'supplemental_address_1', - 'supplemental_address_2', - 'supplemental_address_3', - 'StateProvince.name', - ]; - - foreach ($addressFields as $field) { - if (array_key_exists($field, $values)) { - if (!array_key_exists('address', $params)) { - $params['address'] = []; - } - $params['address'][$addressCnt][$field] = $values[$field]; - } - } - - if ($addressCnt == 1) { - - $params['address'][$addressCnt]['is_primary'] = TRUE; - } - return TRUE; - } - } - /** * Get row count for the event selector. * diff --git a/www/modules/civicrm/CRM/Mailing/Event/BAO/MailingEventOpened.php b/www/modules/civicrm/CRM/Mailing/Event/BAO/MailingEventOpened.php index 52693a221..1c4470746 100644 --- a/www/modules/civicrm/CRM/Mailing/Event/BAO/MailingEventOpened.php +++ b/www/modules/civicrm/CRM/Mailing/Event/BAO/MailingEventOpened.php @@ -26,20 +26,17 @@ class CRM_Mailing_Event_BAO_MailingEventOpened extends CRM_Mailing_Event_DAO_Mai */ public static function open($queue_id) { // First make sure there's a matching queue event. - - $success = FALSE; - $q = new CRM_Mailing_Event_BAO_MailingEventQueue(); $q->id = $queue_id; if ($q->find(TRUE)) { - $oe = new CRM_Mailing_Event_BAO_MailingEventOpened(); - $oe->event_queue_id = $queue_id; - $oe->time_stamp = date('YmdHis'); - $oe->save(); - $success = TRUE; + self::writeRecord([ + 'event_queue_id' => $queue_id, + 'time_stamp' => date('YmdHis'), + ]); + return TRUE; } - return $success; + return FALSE; } /** @@ -100,7 +97,7 @@ public static function getTotalCount( return $dao->N; } else { - return $dao->opened ? $dao->opened : 0; + return $dao->opened ?: 0; } } diff --git a/www/modules/civicrm/CRM/Mailing/Event/BAO/MailingEventResubscribe.php b/www/modules/civicrm/CRM/Mailing/Event/BAO/MailingEventResubscribe.php index 2c1192a71..159d5e79a 100644 --- a/www/modules/civicrm/CRM/Mailing/Event/BAO/MailingEventResubscribe.php +++ b/www/modules/civicrm/CRM/Mailing/Event/BAO/MailingEventResubscribe.php @@ -180,9 +180,6 @@ public static function &resub_to_mailing($job_id, $queue_id, $hash) { public static function send_resub_response($queue_id, $groups, $job) { // param is_domain is not supported as of now. - $config = CRM_Core_Config::singleton(); - $domain = CRM_Core_BAO_Domain::getDomain(); - $jobTable = CRM_Mailing_BAO_MailingJob::getTableName(); $mailingTable = CRM_Mailing_DAO_Mailing::getTableName(); $contacts = CRM_Contact_DAO_Contact::getTableName(); @@ -229,18 +226,18 @@ public static function send_resub_response($queue_id, $groups, $job) { } } - list($addresses, $urls) = CRM_Mailing_BAO_Mailing::getVerpAndUrls($job, $queue_id, $eq->hash, $eq->email); + list($addresses, $urls) = CRM_Mailing_BAO_Mailing::getVerpAndUrls($job, $queue_id, $eq->hash); $bao = new CRM_Mailing_BAO_Mailing(); $bao->body_text = $text; $bao->body_html = $html; $tokens = $bao->getTokens(); $templates = $bao->getTemplates(); - $html = CRM_Utils_Token::replaceResubscribeTokens($templates['html'], $domain, $groups, TRUE, $eq->contact_id, $eq->hash); + $html = CRM_Utils_Token::replaceResubscribeTokens($templates['html'], NULL, $groups); $html = CRM_Utils_Token::replaceActionTokens($html, $addresses, $urls, TRUE, $tokens['html']); $html = CRM_Utils_Token::replaceMailingTokens($html, $dao, NULL, $tokens['html']); - $text = CRM_Utils_Token::replaceResubscribeTokens($templates['text'], $domain, $groups, FALSE, $eq->contact_id, $eq->hash); + $text = CRM_Utils_Token::replaceResubscribeTokens($templates['text'], NULL, $groups); $text = CRM_Utils_Token::replaceActionTokens($text, $addresses, $urls, FALSE, $tokens['text']); $text = CRM_Utils_Token::replaceMailingTokens($text, $dao, NULL, $tokens['text']); diff --git a/www/modules/civicrm/CRM/Mailing/Event/BAO/MailingEventUnsubscribe.php b/www/modules/civicrm/CRM/Mailing/Event/BAO/MailingEventUnsubscribe.php index e831a5e17..3216734b7 100644 --- a/www/modules/civicrm/CRM/Mailing/Event/BAO/MailingEventUnsubscribe.php +++ b/www/modules/civicrm/CRM/Mailing/Event/BAO/MailingEventUnsubscribe.php @@ -392,7 +392,7 @@ public static function send_unsub_response($queue_id, $groups, $is_domain, $job) } } - [$addresses, $urls] = CRM_Mailing_BAO_Mailing::getVerpAndUrls($job, $queue_id, $eq->hash, $eq->email); + [$addresses, $urls] = CRM_Mailing_BAO_Mailing::getVerpAndUrls($job, $queue_id, $eq->hash); $bao = new CRM_Mailing_BAO_Mailing(); $bao->body_text = $text; $bao->body_html = $html; @@ -501,7 +501,7 @@ public static function getTotalCount( return $dao->N; } else { - return $dao->unsubs ? $dao->unsubs : 0; + return $dao->unsubs ?: 0; } } diff --git a/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventBounce.php b/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventBounce.php index 563173f24..9f516a947 100644 --- a/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventBounce.php +++ b/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventBounce.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Mailing/Event/MailingEventBounce.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:393a94b1b88af6753a4d32e77344eef3) + * (GenCodeChecksum:1b94272a41cbe10d181eea827015f7b2) */ /** diff --git a/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventConfirm.php b/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventConfirm.php index a5e7305d2..2392145aa 100644 --- a/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventConfirm.php +++ b/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventConfirm.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Mailing/Event/MailingEventConfirm.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:16533bf0c855436ec5a131998f481dcc) + * (GenCodeChecksum:6034240899b98a7372d88220354b56dd) */ /** diff --git a/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventDelivered.php b/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventDelivered.php index 1d3df4223..2163a74a9 100644 --- a/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventDelivered.php +++ b/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventDelivered.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Mailing/Event/MailingEventDelivered.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:8cbbe3fbdde3f1093237bf19ac9bd9e9) + * (GenCodeChecksum:9a4292365965a8ed87d2306d3e0ca8a3) */ /** diff --git a/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventForward.php b/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventForward.php index 873ff1c24..fdc7f1cd3 100644 --- a/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventForward.php +++ b/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventForward.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Mailing/Event/MailingEventForward.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:610d9e35e6dee904928285f3a6d23f3d) + * (GenCodeChecksum:4fc7f97b199347f2a40350082ece94db) */ /** diff --git a/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventOpened.php b/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventOpened.php index b7d609f48..bd9babf8e 100644 --- a/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventOpened.php +++ b/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventOpened.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Mailing/Event/MailingEventOpened.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:0da200b083ab097f3efdafa091b209a0) + * (GenCodeChecksum:efe3bc021c4b1063acb8ebc6a088538f) */ /** diff --git a/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventQueue.php b/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventQueue.php index b66c42b14..bc184b68c 100644 --- a/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventQueue.php +++ b/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventQueue.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Mailing/Event/MailingEventQueue.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:2b49f3437ecc6f6d2bf0c45370cb1089) + * (GenCodeChecksum:953dd56586ae02b7dde48a338f0827ba) */ /** @@ -248,6 +248,7 @@ public static function &fields() { 'FKClassName' => 'CRM_Core_DAO_Email', 'FKColumnName' => 'id', 'html' => [ + 'type' => 'EntityRef', 'label' => ts("Email"), ], 'add' => NULL, diff --git a/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventReply.php b/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventReply.php index 7869d367d..1eaf63c7b 100644 --- a/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventReply.php +++ b/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventReply.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Mailing/Event/MailingEventReply.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:9a804e1c42467f857e7146bcce53d89b) + * (GenCodeChecksum:1559e02bfead0214ea84393304ca77ea) */ /** diff --git a/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventSubscribe.php b/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventSubscribe.php index 85fb1a63b..26a4d147a 100644 --- a/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventSubscribe.php +++ b/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventSubscribe.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Mailing/Event/MailingEventSubscribe.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:4a66301754363df0906c1c83eaaf7962) + * (GenCodeChecksum:20a178fffc26093c9019d39d72eef535) */ /** diff --git a/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventTrackableURLOpen.php b/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventTrackableURLOpen.php index b983fd0a4..c868e7a86 100644 --- a/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventTrackableURLOpen.php +++ b/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventTrackableURLOpen.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Mailing/Event/MailingEventTrackableURLOpen.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:c1fa35951b41c5f3e8c12652214fd591) + * (GenCodeChecksum:e16a7e0eb8534eac016ca1780a123e85) */ /** diff --git a/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventUnsubscribe.php b/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventUnsubscribe.php index a5d00022f..57a4c9f97 100644 --- a/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventUnsubscribe.php +++ b/www/modules/civicrm/CRM/Mailing/Event/DAO/MailingEventUnsubscribe.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Mailing/Event/MailingEventUnsubscribe.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:896e4dab857ee0a19da7d4ee899c1084) + * (GenCodeChecksum:41ff3d2af16124ad3682a8b056b0a99f) */ /** diff --git a/www/modules/civicrm/CRM/Mailing/Form/Approve.php b/www/modules/civicrm/CRM/Mailing/Form/Approve.php index a777d4f5b..78203d467 100644 --- a/www/modules/civicrm/CRM/Mailing/Form/Approve.php +++ b/www/modules/civicrm/CRM/Mailing/Form/Approve.php @@ -119,7 +119,7 @@ public function buildQuickform() { $preview['type'] = $this->_mailing->body_html ? 'html' : 'text'; $preview['attachment'] = CRM_Core_BAO_File::attachmentInfo('civicrm_mailing', $this->_mailingID); - $this->assign_by_ref('preview', $preview); + $this->assign('preview', $preview); } /** diff --git a/www/modules/civicrm/CRM/Mailing/Form/Component.php b/www/modules/civicrm/CRM/Mailing/Form/Component.php index 603317c7b..d35758184 100644 --- a/www/modules/civicrm/CRM/Mailing/Form/Component.php +++ b/www/modules/civicrm/CRM/Mailing/Form/Component.php @@ -14,6 +14,7 @@ * @package CRM * @copyright CiviCRM LLC https://civicrm.org/licensing */ +use Civi\Api4\MailingComponent; /** * This class generates form components for Location Type. @@ -88,12 +89,11 @@ public function buildQuickForm() { * Set default values for the form. */ public function setDefaultValues() { - $defaults = []; - $params = []; if (isset($this->_id)) { - $params = ['id' => $this->_id]; - CRM_Mailing_BAO_MailingComponent::retrieve($params, $defaults); + $defaults = MailingComponent::get(FALSE) + ->addWhere('id', '=', $this->_id) + ->execute()->single(); } else { $defaults['is_active'] = 1; @@ -185,4 +185,11 @@ public static function formRule($params, $files, $options) { return empty($errors) ? TRUE : $errors; } + /** + * @return array + */ + protected function getFieldsToExcludeFromPurification(): array { + return ['body_html']; + } + } diff --git a/www/modules/civicrm/CRM/Mailing/Info.php b/www/modules/civicrm/CRM/Mailing/Info.php index b3f0c3371..b36d0027a 100644 --- a/www/modules/civicrm/CRM/Mailing/Info.php +++ b/www/modules/civicrm/CRM/Mailing/Info.php @@ -139,48 +139,37 @@ public static function workflowEnabled() { /** * @inheritDoc - * @param bool $getAllUnconditionally - * @param bool $descriptions - * Whether to return permission descriptions - * - * @return array */ - public function getPermissions($getAllUnconditionally = FALSE, $descriptions = FALSE) { + public function getPermissions(): array { $permissions = [ 'access CiviMail' => [ - ts('access CiviMail'), + 'label' => ts('access CiviMail'), ], 'access CiviMail subscribe/unsubscribe pages' => [ - ts('access CiviMail subscribe/unsubscribe pages'), - ts('Subscribe/unsubscribe from mailing list group'), + 'label' => ts('access CiviMail subscribe/unsubscribe pages'), + 'description' => ts('Subscribe/unsubscribe from mailing list group'), ], 'delete in CiviMail' => [ - ts('delete in CiviMail'), - ts('Delete Mailing'), + 'label' => ts('delete in CiviMail'), + 'description' => ts('Delete Mailing'), ], 'view public CiviMail content' => [ - ts('view public CiviMail content'), + 'label' => ts('view public CiviMail content'), ], ]; - - if (self::workflowEnabled() || $getAllUnconditionally) { - $permissions['create mailings'] = [ - ts('create mailings'), - ]; - $permissions['schedule mailings'] = [ - ts('schedule mailings'), - ]; - $permissions['approve mailings'] = [ - ts('approve mailings'), - ]; - } - - if (!$descriptions) { - foreach ($permissions as $name => $attr) { - $permissions[$name] = array_shift($attr); - } - } - + // Workflow permissions + $permissions['create mailings'] = [ + 'label' => ts('create mailings'), + 'disabled' => !self::workflowEnabled(), + ]; + $permissions['schedule mailings'] = [ + 'label' => ts('schedule mailings'), + 'disabled' => !self::workflowEnabled(), + ]; + $permissions['approve mailings'] = [ + 'label' => ts('approve mailings'), + 'disabled' => !self::workflowEnabled(), + ]; return $permissions; } diff --git a/www/modules/civicrm/CRM/Mailing/Selector/Search.php b/www/modules/civicrm/CRM/Mailing/Selector/Search.php index 8f54d1e88..09b4f7490 100644 --- a/www/modules/civicrm/CRM/Mailing/Selector/Search.php +++ b/www/modules/civicrm/CRM/Mailing/Selector/Search.php @@ -297,7 +297,7 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { 'Contact', $result->contact_id ); - $row['contact_type'] = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ? $result->contact_sub_type : $result->contact_type, FALSE, $result->contact_id + $row['contact_type'] = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ?: $result->contact_type, FALSE, $result->contact_id ); $rows[] = $row; diff --git a/www/modules/civicrm/CRM/Mailing/Service/ListUnsubscribe.php b/www/modules/civicrm/CRM/Mailing/Service/ListUnsubscribe.php index 77a786e1a..17f51d200 100644 --- a/www/modules/civicrm/CRM/Mailing/Service/ListUnsubscribe.php +++ b/www/modules/civicrm/CRM/Mailing/Service/ListUnsubscribe.php @@ -34,6 +34,10 @@ public function alterMailParams(&$params, $context = NULL): void { // This code is a little ugly because it anticipates serving both code-paths. // But the BAO path should be properly killed. Doing so will allow you cleanup this code more. + // SMS messages don't have List-Unsubscribe, so bail early. + if (!array_key_exists('List-Unsubscribe', $params)) { + return; + } if (!in_array($context, ['civimail', 'flexmailer'])) { return; } diff --git a/www/modules/civicrm/CRM/Member/ActionMapping.php b/www/modules/civicrm/CRM/Member/ActionMapping.php index ff62e653d..4a992e51e 100644 --- a/www/modules/civicrm/CRM/Member/ActionMapping.php +++ b/www/modules/civicrm/CRM/Member/ActionMapping.php @@ -35,7 +35,7 @@ public function getEntityName(): string { return 'Membership'; } - public function modifySpec(\Civi\Api4\Service\Spec\RequestSpec $spec) { + public function modifyApiSpec(\Civi\Api4\Service\Spec\RequestSpec $spec) { $spec->getFieldByName('entity_value') ->setLabel(ts('Membership Type')); $spec->getFieldByName('entity_status') @@ -47,7 +47,7 @@ public function getValueLabels(): array { } public function getStatusLabels(?array $entityValue): array { - foreach ($entityValue ?? [] as $membershipType) { + foreach (array_filter($entityValue ?? []) as $membershipType) { if (\CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType', $membershipType, 'auto_renew')) { return \CRM_Core_OptionGroup::values('auto_renew_options'); } @@ -95,13 +95,29 @@ public function createQuery($schedule, $phase, $defaultParams): CRM_Utils_SQL_Se $query['casDateField'] = 'e.' . $query['casDateField']; } + $recurStatuses = \Civi\Api4\ContributionRecur::getFields(FALSE) + ->setLoadOptions(TRUE) + ->addWhere('name', '=', 'contribution_status_id') + ->addSelect('options') + ->execute() + ->first()['options']; + // Exclude the renewals that are cancelled or failed. + $nonRenewStatusIds = array_keys(array_intersect($recurStatuses, ['Cancelled', 'Failed'])); // FIXME: Numbers should be constants. if (in_array(2, $selectedStatuses)) { //auto-renew memberships - $query->where("e.contribution_recur_id IS NOT NULL"); + $query->join('cr', 'INNER JOIN civicrm_contribution_recur cr on e.contribution_recur_id = cr.id'); + $query->where("cr.contribution_status_id NOT IN (#nonRenewStatusIds)") + ->param('nonRenewStatusIds', $nonRenewStatusIds); } elseif (in_array(1, $selectedStatuses)) { - $query->where("e.contribution_recur_id IS NULL"); + // non-auto-renew memberships + // Include the renewals that were cancelled or Failed. + $query->join('cr', 'LEFT JOIN civicrm_contribution_recur cr on e.contribution_recur_id = cr.id'); + $query->where("e.contribution_recur_id IS NULL OR ( + e.contribution_recur_id IS NOT NULL AND cr.contribution_status_id IN (#nonRenewStatusIds) + )") + ->param('nonRenewStatusIds', $nonRenewStatusIds); } if (!empty($selectedValues)) { diff --git a/www/modules/civicrm/CRM/Member/BAO/Membership.php b/www/modules/civicrm/CRM/Member/BAO/Membership.php index e22245436..79015e14a 100644 --- a/www/modules/civicrm/CRM/Member/BAO/Membership.php +++ b/www/modules/civicrm/CRM/Member/BAO/Membership.php @@ -1655,12 +1655,18 @@ public static function isCancelSubscriptionSupported($mid, $isNotCancelled = TRU * @throws \CRM_Core_Exception */ public static function isSubscriptionCancelled(int $membershipID): bool { - // Check permissions set to false 'in case' - ideally would check permissions are - // correct & remove. - return (bool) Membership::get(FALSE) - ->addWhere('id', '=', $membershipID) - ->addWhere('contribution_recur_id.contribution_status_id:name', '=', 'Cancelled') - ->selectRowCount()->execute()->count(); + $isCiviContributeEnabled = CRM_Extension_System::singleton() + ->getManager() + ->isEnabled('civi_contribute'); + if ($isCiviContributeEnabled) { + // Check permissions set to false 'in case' - ideally would check permissions are + // correct & remove. + return (bool) Membership::get(FALSE) + ->addWhere('id', '=', $membershipID) + ->addWhere('contribution_recur_id.contribution_status_id:name', '=', 'Cancelled') + ->selectRowCount()->execute()->count(); + } + return FALSE; } /** @@ -1768,229 +1774,6 @@ public static function getMembershipRenewals($membershipTypeId, $startDate, $end return (int) $memberCount; } - /** - * @deprecated in CiviCRM 5.39, will be removed around CiviCRM 5.66. - * - * Deprecation issue in https://lab.civicrm.org/extensions/civimobileapi/-/issues/86 - * - * @param int $contactID - * @param int $membershipTypeID - * @param bool $is_test - * @param string $changeToday - * @param int $modifiedID - * @param $customFieldsFormatted - * @param $numRenewTerms - * @param int $membershipID - * @param $pending - * @param int $contributionRecurID - * @param $membershipSource - * @param $isPayLater - * @param array $memParams - * @param null|CRM_Contribute_BAO_Contribution $contribution - * @param array $lineItems - * - * @return array - * @throws \CRM_Core_Exception - */ - public static function processMembership($contactID, $membershipTypeID, $is_test, $changeToday, $modifiedID, $customFieldsFormatted, $numRenewTerms, $membershipID, $pending, $contributionRecurID, $membershipSource, $isPayLater, $memParams = [], $contribution = NULL, $lineItems = []) { - CRM_Core_Error::deprecatedFunctionWarning('use the order api, BAO functions should only be called from unit tested core code.'); - $renewalMode = $updateStatusId = FALSE; - $allStatus = CRM_Member_PseudoConstant::membershipStatus(); - $format = '%Y%m%d'; - $statusFormat = '%Y-%m-%d'; - $membershipTypeDetails = CRM_Member_BAO_MembershipType::getMembershipType($membershipTypeID); - $dates = []; - $ids = []; - - // CRM-7297 - allow membership type to be be changed during renewal so long as the parent org of new membershipType - // is the same as the parent org of an existing membership of the contact - $currentMembership = CRM_Member_BAO_Membership::getContactMembership($contactID, $membershipTypeID, - $is_test, $membershipID, TRUE - ); - if ($currentMembership) { - $renewalMode = TRUE; - - // Do NOT do anything. - //1. membership with status : PENDING/CANCELLED (CRM-2395) - //2. Paylater/IPN renew. CRM-4556. - if ($pending || in_array($currentMembership['status_id'], [ - array_search('Pending', $allStatus), - // CRM-15475 - array_search('Cancelled', CRM_Member_PseudoConstant::membershipStatus(NULL, " name = 'Cancelled' ", 'name', FALSE, TRUE)), - ])) { - - $memParams = array_merge([ - 'id' => $currentMembership['id'], - 'contribution' => $contribution, - 'status_id' => $currentMembership['status_id'], - 'start_date' => $currentMembership['start_date'], - 'end_date' => $currentMembership['end_date'], - 'line_item' => $lineItems, - 'join_date' => $currentMembership['join_date'], - 'membership_type_id' => $membershipTypeID, - 'max_related' => !empty($membershipTypeDetails['max_related']) ? $membershipTypeDetails['max_related'] : NULL, - 'membership_activity_status' => ($pending || $isPayLater) ? 'Scheduled' : 'Completed', - ], $memParams); - if ($contributionRecurID) { - $memParams['contribution_recur_id'] = $contributionRecurID; - } - - $membership = self::create($memParams); - return [$membership, $renewalMode, $dates]; - } - - // Check and fix the membership if it is STALE - self::fixMembershipStatusBeforeRenew($currentMembership, $changeToday); - - // Now Renew the membership - if (!$currentMembership['is_current_member']) { - // membership is not CURRENT - - // CRM-7297 Membership Upsell - calculate dates based on new membership type - $dates = CRM_Member_BAO_MembershipType::getRenewalDatesForMembershipType($currentMembership['id'], - $changeToday, - $membershipTypeID, - $numRenewTerms - ); - - $currentMembership['join_date'] = CRM_Utils_Date::customFormat($currentMembership['join_date'], $format); - foreach (['start_date', 'end_date'] as $dateType) { - $currentMembership[$dateType] = $dates[$dateType] ?? NULL; - } - $currentMembership['is_test'] = $is_test; - - if (!empty($membershipSource)) { - $currentMembership['source'] = $membershipSource; - } - - if (!empty($currentMembership['id'])) { - $ids['membership'] = $currentMembership['id']; - } - $memParams = array_merge($currentMembership, $memParams); - $memParams['membership_type_id'] = $membershipTypeID; - - //set the log start date. - $memParams['log_start_date'] = CRM_Utils_Date::customFormat($dates['log_start_date'], $format); - } - else { - - // CURRENT Membership - $membership = new CRM_Member_DAO_Membership(); - $membership->id = $currentMembership['id']; - $membership->find(TRUE); - // CRM-7297 Membership Upsell - calculate dates based on new membership type - $dates = CRM_Member_BAO_MembershipType::getRenewalDatesForMembershipType($membership->id, - $changeToday, - $membershipTypeID, - $numRenewTerms - ); - - // Insert renewed dates for CURRENT membership - $memParams['join_date'] = CRM_Utils_Date::isoToMysql($membership->join_date); - $memParams['start_date'] = CRM_Utils_Date::isoToMysql($membership->start_date); - $memParams['end_date'] = $dates['end_date'] ?? NULL; - $memParams['membership_type_id'] = $membershipTypeID; - - //set the log start date. - $memParams['log_start_date'] = CRM_Utils_Date::customFormat($dates['log_start_date'], $format); - - //CRM-18067 - if (!empty($membershipSource)) { - $memParams['source'] = $membershipSource; - } - elseif (empty($membership->source)) { - $memParams['source'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership', - $currentMembership['id'], - 'source' - ); - } - - if (!empty($currentMembership['id'])) { - $ids['membership'] = $currentMembership['id']; - } - $memParams['membership_activity_status'] = ($pending || $isPayLater) ? 'Scheduled' : 'Completed'; - } - } - else { - // NEW Membership - $memParams = array_merge([ - 'contact_id' => $contactID, - 'membership_type_id' => $membershipTypeID, - ], $memParams); - - if (!$pending) { - $dates = CRM_Member_BAO_MembershipType::getDatesForMembershipType($membershipTypeID, NULL, NULL, NULL, $numRenewTerms); - - foreach (['join_date', 'start_date', 'end_date'] as $dateType) { - $memParams[$dateType] = $dates[$dateType] ?? NULL; - } - - $status = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate(CRM_Utils_Date::customFormat($dates['start_date'], - $statusFormat - ), - CRM_Utils_Date::customFormat($dates['end_date'], - $statusFormat - ), - CRM_Utils_Date::customFormat($dates['join_date'], - $statusFormat - ), - 'now', - TRUE, - $membershipTypeID, - $memParams - ); - $updateStatusId = $status['id'] ?? NULL; - } - else { - // if IPN/Pay-Later set status to: PENDING - $updateStatusId = array_search('Pending', $allStatus); - } - - if (!empty($membershipSource)) { - $memParams['source'] = $membershipSource; - } - $memParams['is_test'] = $is_test; - $memParams['is_pay_later'] = $isPayLater; - } - // Putting this in an IF is precautionary as it seems likely that it would be ignored if empty, but - // perhaps shouldn't be? - if ($contributionRecurID) { - $memParams['contribution_recur_id'] = $contributionRecurID; - } - //CRM-4555 - //if we decided status here and want to skip status - //calculation in create( ); then need to pass 'skipStatusCal'. - if ($updateStatusId) { - $memParams['status_id'] = $updateStatusId; - $memParams['skipStatusCal'] = TRUE; - } - - //since we are renewing, - //make status override false. - $memParams['is_override'] = FALSE; - - //CRM-4027, create log w/ individual contact. - if ($modifiedID) { - // @todo this param is likely unused now. - $memParams['is_for_organization'] = TRUE; - } - $params['modified_id'] = $modifiedID ?? $contactID; - - $memParams['contribution'] = $contribution; - $memParams['custom'] = $customFieldsFormatted; - // Load all line items & process all in membership. Don't do in contribution. - // Relevant tests in api_v3_ContributionPageTest. - $memParams['line_item'] = $lineItems; - // @todo stop passing $ids (membership and userId may be set by this point) - $membership = self::create($memParams, $ids); - - // not sure why this statement is here, seems quite odd :( - Lobo: 12/26/2010 - // related to: http://forum.civicrm.org/index.php/topic,11416.msg49072.html#msg49072 - $membership->find(TRUE); - - return [$membership, $renewalMode, $dates]; - } - /** * Get line items representing the default price set. * diff --git a/www/modules/civicrm/CRM/Member/DAO/Membership.php b/www/modules/civicrm/CRM/Member/DAO/Membership.php index 030554f3a..588cb81be 100644 --- a/www/modules/civicrm/CRM/Member/DAO/Membership.php +++ b/www/modules/civicrm/CRM/Member/DAO/Membership.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Member/Membership.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:663eaa9d986f476eb6721540840a643d) + * (GenCodeChecksum:cdedf93fcfc840a82de58b5e9defb24e) */ /** diff --git a/www/modules/civicrm/CRM/Member/DAO/MembershipBlock.php b/www/modules/civicrm/CRM/Member/DAO/MembershipBlock.php index 39a97a6c8..a6db1f169 100644 --- a/www/modules/civicrm/CRM/Member/DAO/MembershipBlock.php +++ b/www/modules/civicrm/CRM/Member/DAO/MembershipBlock.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Member/MembershipBlock.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:8570f61c5c934d765c3d8d47edcfdc36) + * (GenCodeChecksum:8b008760c202b35267e3bb8490afa031) */ /** diff --git a/www/modules/civicrm/CRM/Member/DAO/MembershipLog.php b/www/modules/civicrm/CRM/Member/DAO/MembershipLog.php index 2525d76ab..88bb10708 100644 --- a/www/modules/civicrm/CRM/Member/DAO/MembershipLog.php +++ b/www/modules/civicrm/CRM/Member/DAO/MembershipLog.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Member/MembershipLog.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:ef568df305bf840b719c242e610d6519) + * (GenCodeChecksum:1dc919ae29f4b80d0faf1797c0b201a4) */ /** diff --git a/www/modules/civicrm/CRM/Member/DAO/MembershipPayment.php b/www/modules/civicrm/CRM/Member/DAO/MembershipPayment.php index 21edc4d3b..b62551cff 100644 --- a/www/modules/civicrm/CRM/Member/DAO/MembershipPayment.php +++ b/www/modules/civicrm/CRM/Member/DAO/MembershipPayment.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Member/MembershipPayment.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:44c05454d437ff852f4cf0a9b4b005f0) + * (GenCodeChecksum:ec1e8245da2d2a907076d9a608c30e16) */ /** diff --git a/www/modules/civicrm/CRM/Member/DAO/MembershipStatus.php b/www/modules/civicrm/CRM/Member/DAO/MembershipStatus.php index 051e5bf87..f5a3b395d 100644 --- a/www/modules/civicrm/CRM/Member/DAO/MembershipStatus.php +++ b/www/modules/civicrm/CRM/Member/DAO/MembershipStatus.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Member/MembershipStatus.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:667f8df9ffcdc08d37863ce19cb1b0ef) + * (GenCodeChecksum:4321ae17aa4f714583d4b73dfb894641) */ /** @@ -445,6 +445,9 @@ public static function &fields() { 'entity' => 'MembershipStatus', 'bao' => 'CRM_Member_BAO_MembershipStatus', 'localizable' => 0, + 'html' => [ + 'type' => 'CheckBox', + ], 'add' => '1.5', ], 'is_admin' => [ diff --git a/www/modules/civicrm/CRM/Member/DAO/MembershipType.php b/www/modules/civicrm/CRM/Member/DAO/MembershipType.php index 4d1bce9cd..44bf39250 100644 --- a/www/modules/civicrm/CRM/Member/DAO/MembershipType.php +++ b/www/modules/civicrm/CRM/Member/DAO/MembershipType.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Member/MembershipType.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:bf10593b5e20b28573e422fadfe689a7) + * (GenCodeChecksum:4d6009daa56ccbccff414917fd6aeb1b) */ /** @@ -38,6 +38,17 @@ class CRM_Member_DAO_MembershipType extends CRM_Core_DAO { */ public static $_log = TRUE; + /** + * Paths for accessing this entity in the UI. + * + * @var string[] + */ + protected static $_paths = [ + 'add' => 'civicrm/admin/member/membershipType/add?action=add&reset=1', + 'update' => 'civicrm/admin/member/membershipType/add?action=update&id=[id]&reset=1', + 'delete' => 'civicrm/admin/member/membershipType/add?action=delete&id=[id]&reset=1', + ]; + /** * Membership ID * diff --git a/www/modules/civicrm/CRM/Member/Form.php b/www/modules/civicrm/CRM/Member/Form.php index 03c1d9e17..fc8509094 100644 --- a/www/modules/civicrm/CRM/Member/Form.php +++ b/www/modules/civicrm/CRM/Member/Form.php @@ -20,7 +20,7 @@ * */ class CRM_Member_Form extends CRM_Contribute_Form_AbstractEditPayment { - + use CRM_Custom_Form_CustomDataTrait; use CRM_Core_Form_EntityFormTrait; /** @@ -72,6 +72,8 @@ class CRM_Member_Form extends CRM_Contribute_Form_AbstractEditPayment { * Price set ID configured for the form. * * @var int + * + * @deprecated use getPriceSetID() */ public $_priceSetId; @@ -254,9 +256,9 @@ public function setDefaultValues() { if (!empty($defaults['is_override'])) { $defaults['is_override'] = CRM_Member_StatusOverrideTypes::PERMANENT; - } - if (!empty($defaults['status_override_end_date'])) { - $defaults['is_override'] = CRM_Member_StatusOverrideTypes::UNTIL_DATE; + if (!empty($defaults['status_override_end_date'])) { + $defaults['is_override'] = CRM_Member_StatusOverrideTypes::UNTIL_DATE; + } } } @@ -551,20 +553,37 @@ protected function getPriceSetDetails(array $params): ?array { } /** - * Get the selected price set id. + * Get the price set ID. * - * @param array $params - * Parameters submitted to the form. + * @api Supported for external use. * * @return int */ - protected function getPriceSetID(array $params): int { - $priceSetID = $params['price_set_id'] ?? NULL; - if (!$priceSetID) { - $priceSetDetails = $this->getPriceSetDetails($params); - return (int) key($priceSetDetails); + public function getPriceSetID(): int { + $this->_priceSetId = $this->isAjaxOverLoadMode() ? CRM_Utils_Request::retrieve('priceSetId', 'Integer') : ($this->getSubmittedValue('price_set_id') ?? NULL); + if (!$this->_priceSetId) { + $priceSet = CRM_Price_BAO_PriceSet::getDefaultPriceSet('membership'); + $priceSet = reset($priceSet); + $this->_priceSetId = (int) $priceSet['setID']; } - return (int) $priceSetID; + return (int) $this->_priceSetId; + } + + /** + * Is the form being called in ajax overload mode. + * + * Ajax overload mode is what the form is called via ajax to render the price + * form, without the rest of the processing. This is a legacy of a time when + * we didn't have better ways to do this. We only render the price set form + * on overload mode, but we need to ensure the fields are added to the + * form when the form is submitted so QuickForm sees the fields. Over time + * we have broken this approach for the payment form and custom data form + * and may do so here one day.... + * + * @return bool + */ + protected function isAjaxOverLoadMode(): bool { + return !empty($_GET['priceSetId']); } /** @@ -577,7 +596,7 @@ protected function getPriceSetID(array $params): int { */ protected function setPriceSetParameters(array $formValues): array { // process price set and get total amount and line items. - $this->_priceSetId = $this->getPriceSetID($formValues); + $this->getPriceSetID(); $this->ensurePriceParamsAreSet($formValues); $priceSetDetails = $this->getPriceSetDetails($formValues); $this->_priceSet = $priceSetDetails[$this->_priceSetId]; @@ -585,12 +604,12 @@ protected function setPriceSetParameters(array $formValues): array { $this->order->setForm($this); $this->order->setPriceSelectionFromUnfilteredInput($formValues); - if (isset($formValues['total_amount'])) { - $this->order->setOverrideTotalAmount((float) $formValues['total_amount']); + if ($this->getSubmittedValue('total_amount')) { + $this->order->setOverrideTotalAmount((float) $this->getSubmittedValue('total_amount')); } - if ($this->isQuickConfig()) { - $this->order->setOverrideFinancialTypeID((int) $formValues['financial_type_id']); + if ($this->isQuickConfig() && $this->getSubmittedValue('financial_type_id')) { + $this->order->setOverrideFinancialTypeID((int) $this->getSubmittedValue('financial_type_id')); } return $formValues; @@ -601,8 +620,25 @@ protected function setPriceSetParameters(array $formValues): array { * * @return bool */ - private function isQuickConfig(): bool { - return $this->_priceSetId && CRM_Price_BAO_PriceSet::isQuickConfig($this->_priceSetId); + protected function isQuickConfig(): bool { + return $this->getPriceSetID() && CRM_Price_BAO_PriceSet::isQuickConfig($this->getPriceSetID()); + } + + /** + * Overriding this entity trait function as the function does not + * at this stage use the CustomDataTrait which works better with php8.2. + */ + public function addCustomDataToForm() { + if ($this->isSubmitted()) { + // The custom data fields are added to the form by an ajax form. + // However, if they are not present in the element index they will + // not be available from `$this->getSubmittedValue()` in post process. + // We do not have to set defaults or otherwise render - just add to the element index. + $this->addCustomDataFieldsToForm('Membership', array_filter([ + 'id' => $this->getMembershipID(), + 'membership_type_id' => $this->getSubmittedValue('membership_type_id'), + ])); + } } /** diff --git a/www/modules/civicrm/CRM/Member/Form/Membership.php b/www/modules/civicrm/CRM/Member/Form/Membership.php index 42262451c..978ca4e5c 100644 --- a/www/modules/civicrm/CRM/Member/Form/Membership.php +++ b/www/modules/civicrm/CRM/Member/Form/Membership.php @@ -74,13 +74,6 @@ public function setDeleteMessage() { . ts("Click 'Delete' if you want to continue.") . '

'; } - /** - * Overriding this entity trait function as not yet tested. - * - * We continue to rely on legacy handling. - */ - public function addCustomDataToForm() {} - /** * Overriding this entity trait function as not yet tested. * @@ -154,8 +147,14 @@ public function preProcess() { // get price set id. $this->_priceSetId = $_GET['priceSetId'] ?? NULL; - $this->set('priceSetId', $this->_priceSetId); - $this->assign('priceSetId', $this->_priceSetId); + // This is assigned only for the purposes of displaying the price block + // INSTEAD of the edit form. ie when the form is being overloaded + // via ajax (which is a long-standing anti-pattern - in + // some cases we have moved that overload behaviour + // to a separate form but note that it is necessary to + // add the fields to QuickForm when the form + // has been submitted so they appear in submittedValues(). + $this->assign('priceSetId', $_GET['priceSetId'] ?? NULL); if ($this->_action & CRM_Core_Action::DELETE) { $contributionID = CRM_Member_BAO_Membership::getMembershipContributionId($this->_id); @@ -224,8 +223,9 @@ public function preProcess() { } } - // Add custom data to form - CRM_Custom_Form_CustomData::addToForm($this, $this->_memType); + $this->assign('customDataType', 'Membership'); + $this->assign('customDataSubType', $this->_memType); + $this->setPageTitle(ts('Membership')); } @@ -323,7 +323,7 @@ public function buildQuickForm() { $isUpdateToExistingRecurringMembership = $this->isUpdateToExistingRecurringMembership(); // build price set form. $buildPriceSet = FALSE; - if ($this->_priceSetId || !empty($_POST['price_set_id'])) { + if ($this->isAjaxOverLoadMode() || !empty($_POST['price_set_id'])) { if (!empty($_POST['price_set_id'])) { $buildPriceSet = TRUE; } @@ -333,11 +333,10 @@ public function buildQuickForm() { $getOnlyPriceSetElements = FALSE; } - $this->set('priceSetId', $this->_priceSetId); - CRM_Price_BAO_PriceSet::buildPriceSet($this, 'membership', FALSE); + $this->buildMembershipPriceSet(); $optionsMembershipTypes = []; - foreach ($this->_priceSet['fields'] as $pField) { + foreach ($this->getPriceFieldMetaData() as $pField) { if (empty($pField['options'])) { continue; } @@ -349,7 +348,7 @@ public function buildQuickForm() { $this->assign('autoRenewOption', CRM_Price_BAO_PriceSet::checkAutoRenewForPriceSet($this->_priceSetId)); $this->assign('optionsMembershipTypes', $optionsMembershipTypes); - $this->assign('contributionType', CRM_Utils_Array::value('financial_type_id', $this->_priceSet)); + $this->assign('contributionType', $this->_priceSet['financial_type_id'] ?? NULL); // get only price set form elements. if ($getOnlyPriceSetElements) { @@ -576,6 +575,64 @@ public function buildQuickForm() { parent::buildQuickForm(); } + /** + * Build the price set form. + * + * @return void + * + * @deprecated this should be updated to align with the other forms that use getOrder() + */ + private function buildMembershipPriceSet() { + $form = $this; + + $this->_priceSet = $this->getOrder()->getPriceSetMetadata(); + $validPriceFieldIds = array_keys($this->getPriceFieldMetaData()); + + // Mark which field should have the auto-renew checkbox, if any. CRM-18305 + // This is probably never set & relates to another form from previously shared code. + if (!empty($form->_membershipTypeValues) && is_array($form->_membershipTypeValues)) { + $autoRenewMembershipTypes = []; + foreach ($form->_membershipTypeValues as $membershipTypeValue) { + if ($membershipTypeValue['auto_renew']) { + $autoRenewMembershipTypes[] = $membershipTypeValue['id']; + } + } + foreach ($form->getPriceFieldMetaData() as $field) { + if (array_key_exists('options', $field) && is_array($field['options'])) { + foreach ($field['options'] as $option) { + if (!empty($option['membership_type_id'])) { + if (in_array($option['membership_type_id'], $autoRenewMembershipTypes)) { + $form->_priceSet['auto_renew_membership_field'] = $field['id']; + // Only one field can offer auto_renew memberships, so break here. + // May not relate to this form? From previously shared code. + break; + } + } + } + } + } + } + $form->assign('priceSet', $form->_priceSet); + + $checklifetime = FALSE; + foreach ($this->getPriceFieldMetaData() as $id => $field) { + $options = $field['options'] ?? NULL; + if (!is_array($options) || !in_array($id, $validPriceFieldIds)) { + continue; + } + if (!empty($options)) { + CRM_Price_BAO_PriceField::addQuickFormElement($form, + 'price_' . $field['id'], + $field['id'], + FALSE, + $field['is_required'] ?? FALSE, + NULL, + $options + ); + } + } + } + /** * Validation. * @@ -594,7 +651,7 @@ public function buildQuickForm() { public static function formRule($params, $files, $self) { $errors = []; - $priceSetId = $self->getPriceSetID($params); + $priceSetId = $self->getPriceSetID(); $priceSetDetails = $self->getPriceSetDetails($params); $selectedMemberships = self::getSelectedMemberships($priceSetDetails[$priceSetId], $params); @@ -788,6 +845,44 @@ public static function formRule($params, $files, $self) { return empty($errors) ? TRUE : $errors; } + /** + * Get price field metadata. + * + * The returned value is an array of arrays where each array + * is an id-keyed price field and an 'options' key has been added to that + * array for any options. + * + * @api - this is not yet being used by the form - only by a test but + * follows standard methodology so should stay the same. + * + * @return array + */ + public function getPriceFieldMetaData(): array { + $this->_priceSet['fields'] = $this->getOrder()->getPriceFieldsMetadata(); + return $this->_priceSet['fields']; + } + + /** + * @return \CRM_Financial_BAO_Order + * @throws \CRM_Core_Exception + */ + protected function getOrder(): CRM_Financial_BAO_Order { + if (!$this->order) { + $this->initializeOrder(); + } + return $this->order; + } + + /** + * @throws \CRM_Core_Exception + */ + protected function initializeOrder(): void { + $this->order = new CRM_Financial_BAO_Order(); + $this->order->setPriceSetID($this->getPriceSetID()); + $this->order->setForm($this); + $this->order->setPriceSelectionFromUnfilteredInput($this->getSubmittedValues()); + } + /** * Process the form submission. * @@ -877,7 +972,7 @@ protected function emailReceipt($formValues) { else { $this->assign('receiptType', 'membership signup'); } - $this->assign('receive_date', CRM_Utils_Array::value('receive_date', $formValues)); + $this->assign('receive_date', $formValues['receive_date'] ?? NULL); $this->assign('formValues', $formValues); $this->assign('mem_start_date', CRM_Utils_Date::formatDateOnlyLong($membership['start_date'])); @@ -1063,7 +1158,7 @@ public function submit(): void { 'line_items' => $this->getLineItemForOrderApi(), 'is_test' => $this->isTest(), 'campaign_id' => $this->getSubmittedValue('campaign_id'), - 'source' => CRM_Utils_Array::value('source', $paymentParams, CRM_Utils_Array::value('description', $paymentParams)), + 'source' => $paymentParams['source'] ?? $paymentParams['description'] ?? NULL, 'payment_instrument_id' => $this->getPaymentInstrumentID(), 'financial_type_id' => $this->getFinancialTypeID(), 'receive_date' => $this->getReceiveDate(), @@ -1158,7 +1253,7 @@ public function submit(): void { } $this->set('params', $formValues); - $this->assign('trxn_id', CRM_Utils_Array::value('trxn_id', $result)); + $this->assign('trxn_id', $result['trxn_id'] ?? NULL); $this->assign('receive_date', CRM_Utils_Date::mysqlToIso($params['receive_date']) ); @@ -1207,10 +1302,23 @@ public function submit(): void { // If we are recording a contribution we *do* want to trigger a recalculation of membership status so it can go from Pending->New/Current // So here we check if status_id is empty, default (ie. status in database) is pending and that we are not recording a contribution - // If all those are true then we skip the status calculation and explicitly set the pending status (to avoid a DB constraint status_id=0). + // Test cover in `CRM_Member_Form_MembershipTest::testOverrideSubmit()`. + $isPaymentPending = FALSE; + if ($this->getMembershipID()) { + $contributionId = CRM_Member_BAO_Membership::getMembershipContributionId($this->getMembershipID()); + if ($contributionId) { + $isPaymentPending = \Civi\Api4\Contribution::get(FALSE) + ->addSelect('contribution_status_id:name') + ->addWhere('id', '=', $contributionId) + ->execute() + ->first()['contribution_status_id:name'] === 'Pending'; + } + } if (empty($membershipParams['status_id']) && !empty($this->_defaultValues['status_id']) && !$this->getSubmittedValue('record_contribution') && (int) $this->_defaultValues['status_id'] === $pendingMembershipStatusId + && $isPaymentPending ) { $membershipParams['status_id'] = $this->_defaultValues['status_id']; } @@ -1259,8 +1367,12 @@ public function submit(): void { $contributionId = $this->ids['Contribution'] ?? CRM_Member_BAO_Membership::getMembershipContributionId($this->getMembershipID()); $membershipIds = $this->_membershipIDs; if ($this->getSubmittedValue('send_receipt') && $contributionId && !empty($membershipIds)) { - $contributionDetails = CRM_Contribute_BAO_Contribution::getContributionDetails(CRM_Export_Form_Select::MEMBER_EXPORT, $this->_membershipIDs); - if ($contributionDetails[$this->getMembershipID()]['contribution_status'] === 'Completed') { + $contributionStatus = \Civi\Api4\Contribution::get(FALSE) + ->addSelect('contribution_status_id:name') + ->addWhere('id', '=', $contributionId) + ->execute() + ->first(); + if ($contributionStatus['contribution_status_id:name'] === 'Completed') { $formValues['contact_id'] = $this->_contactID; $formValues['contribution_id'] = $contributionId; // receipt_text_signup is no longer used in receipts from 5.47 @@ -1385,6 +1497,13 @@ protected function setUserContext() { $url = CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$this->_contactID}&selectedChild=member" ); + // Refresh other tabs with related data + $this->ajaxResponse['updateTabs'] = [ + '#tab_activity' => TRUE, + ]; + if (CRM_Core_Permission::access('CiviContribute')) { + $this->ajaxResponse['updateTabs']['#tab_contribute'] = CRM_Contact_BAO_Contact::getCountComponent('contribution', $this->_contactID); + } } $session->replaceUserContext($url); } @@ -1399,7 +1518,7 @@ protected function getStatusMessageForUpdate(): string { foreach ($this->getCreatedMemberships() as $membership) { $endDate = $membership['end_date'] ?? NULL; } - $statusMsg = ts('Membership for %1 has been updated.', [1 => $this->_memberDisplayName]); + $statusMsg = ts('Membership for %1 has been updated.', [1 => htmlentities($this->_memberDisplayName)]); if ($endDate) { $endDate = CRM_Utils_Date::customFormat($endDate); $statusMsg .= ' ' . ts('The Membership Expiration Date is %1.', [1 => $endDate]); @@ -1417,7 +1536,7 @@ protected function getStatusMessageForCreate(): string { foreach ($this->getCreatedMemberships() as $membership) { $statusMsg[$membership['membership_type_id']] = ts('%1 membership for %2 has been added.', [ 1 => $this->allMembershipTypeDetails[$membership['membership_type_id']]['name'], - 2 => $this->_memberDisplayName, + 2 => htmlentities($this->_memberDisplayName), ]); $memEndDate = $membership['end_date'] ?? NULL; @@ -1485,7 +1604,7 @@ protected function isUpdateToExistingRecurringMembership() { * @throws \CRM_Core_Exception */ protected function emailMembershipReceipt($formValues) { - $customValues = $this->getCustomValuesForReceipt($formValues); + $customValues = $this->getCustomValuesForReceipt(); $this->assign('customValues', $customValues); $this->assign('total_amount', $this->order->getTotalAmount()); $this->assign('totalTaxAmount', $this->order->getTotalTaxAmount()); @@ -1511,35 +1630,46 @@ protected function emailMembershipReceipt($formValues) { /** * Filter the custom values from the input parameters (for display in the email). * - * @todo figure out why the scary code this calls does & document. + * @todo move this to the WorkFlowMessageTrait. * - * @param array $formValues * @return array */ - protected function getCustomValuesForReceipt($formValues): array { - $customFields = $customValues = []; - if (property_exists($this, '_groupTree') - && !empty($this->_groupTree) - ) { - foreach ($this->_groupTree as $groupID => $group) { - if ($groupID === 'info') { - continue; - } - foreach ($group['fields'] as $k => $field) { - $field['title'] = $field['label']; - $customFields["custom_{$k}"] = $field; - } + protected function getCustomValuesForReceipt(): array { + $customFieldValues = $this->getCustomFieldValues(); + $customValues = []; + foreach ($customFieldValues as $customFieldString => $submittedValue) { + if ($submittedValue === NULL) { + // This would occur when the field is no longer appropriate - ie changing + // from a membership type with the field to one without it. + continue; } + $customFieldID = (int) CRM_Core_BAO_CustomField::getKeyID($customFieldString); + $value = CRM_Core_BAO_CustomField::displayValue($this->getSubmittedValue($customFieldString), $customFieldID); + $customValues[CRM_Core_BAO_CustomField::getField($customFieldID)['label']] = $value; } + return $customValues; + } - $members = [['member_id', '=', $this->getMembershipID(), 0, 0]]; - // check whether its a test drive - if ($this->_mode === 'test') { - $members[] = ['member_test', '=', 1, 0, 0]; + /** + * Get the custom fields tat are on the form with the submitted values. + * + * @internal I think it would be good to make this field + * available as an api supported method - maybe on the form custom data trait + * but I feel like we might want to talk about the format of the returned results + * (key-value vs array, custom field ID keys vs custom_, filters/ multiple functions + * with different formatting) so keeping private for now. + * + * @return array + */ + private function getCustomFieldValues(): array { + $customFields = []; + foreach ($this->_elements as $element) { + $customFieldID = CRM_Core_BAO_CustomField::getKeyID($element->getName()); + if ($customFieldID) { + $customFields[$element->getName()] = $this->getSubmittedValue($element->getName()); + } } - - CRM_Core_BAO_UFGroup::getValues($formValues['contact_id'], $customFields, $customValues, FALSE, $members); - return $customValues; + return $customFields; } /** @@ -1678,7 +1808,6 @@ protected function getFrequencyUnit(): ?string { * @throws \CRM_Core_Exception */ protected function getFormMembershipParams(): array { - $submittedValues = $this->controller->exportValues($this->_name); return [ 'status_id' => $this->getSubmittedValue('status_id'), 'source' => $this->getSubmittedValue('source') ?? $this->getContributionSource(), @@ -1686,7 +1815,7 @@ protected function getFormMembershipParams(): array { 'is_override' => $this->getSubmittedValue('is_override'), 'status_override_end_date' => $this->getSubmittedValue('status_override_end_date'), 'campaign_id' => $this->getSubmittedValue('campaign_id'), - 'custom' => CRM_Core_BAO_CustomField::postProcess($submittedValues, + 'custom' => CRM_Core_BAO_CustomField::postProcess($this->getSubmittedValues(), $this->_id, 'Membership' ), @@ -1742,9 +1871,20 @@ protected function getMembershipParameters(): array { $membershipTypeValues[$memType]['max_related'] = $this->getSubmittedValue('max_related'); } } - + // Really we don't need to do all this unless one of the join dates + // has been left empty by the submitter - ie in an ADD scenario but not really + // valid when editing & it would possibly not get the number of terms right + // so ideally the fields would be required on edit & the below would only + // be called on ADD foreach ($this->order->getMembershipLineItems() as $membershipLineItem) { - $memTypeNumTerms = $this->getSubmittedValue('num_terms') ?: $membershipLineItem['membership_num_terms']; + if ($this->getAction() === CRM_Core_Action::ADD && $this->isQuickConfig()) { + $memTypeNumTerms = $this->getSubmittedValue('num_terms'); + } + else { + // The submitted value is hidden when a price set is selected so + // although it is present it should be ignored. + $memTypeNumTerms = $membershipLineItem['membership_num_terms']; + } $calcDates = CRM_Member_BAO_MembershipType::getDatesForMembershipType( $membershipLineItem['membership_type_id'], $this->getSubmittedValue('join_date'), diff --git a/www/modules/civicrm/CRM/Member/Form/MembershipRenewal.php b/www/modules/civicrm/CRM/Member/Form/MembershipRenewal.php index ea9c8a110..982838f7f 100644 --- a/www/modules/civicrm/CRM/Member/Form/MembershipRenewal.php +++ b/www/modules/civicrm/CRM/Member/Form/MembershipRenewal.php @@ -165,13 +165,6 @@ public function preProcess() { } } - // when custom data is included in this page - if (!empty($_POST['hidden_custom'])) { - CRM_Custom_Form_CustomData::preProcess($this, NULL, $this->_memType, 1, 'Membership', $this->_id); - CRM_Custom_Form_CustomData::buildQuickForm($this); - CRM_Custom_Form_CustomData::setDefaultValues($this); - } - $this->setTitle(ts('Renew Membership')); parent::preProcess(); @@ -224,7 +217,7 @@ public function setDefaultValues() { $renewalDate = $defaults['renewal_date'] ?? NULL; $this->assign('renewalDate', $renewalDate); - $this->assign('member_is_test', CRM_Utils_Array::value('member_is_test', $defaults)); + $this->assign('member_is_test', $defaults['member_is_test'] ?? NULL); if ($this->_mode) { $defaults = $this->getBillingDefaults($defaults); @@ -240,6 +233,7 @@ public function setDefaultValues() { public function buildQuickForm() { parent::buildQuickForm(); + $this->addCustomDataToForm(); $defaults = parent::setDefaultValues(); $this->assign('customDataType', 'Membership'); @@ -493,13 +487,13 @@ protected function submit() { $this->storeContactFields($this->_params); $this->beginPostProcess(); $now = CRM_Utils_Date::getToday(NULL, 'YmdHis'); - $this->assign('receive_date', CRM_Utils_Array::value('receive_date', $this->_params, CRM_Utils_Time::date('Y-m-d H:i:s'))); + $this->assign('receive_date', $this->_params['receive_date'] ?? CRM_Utils_Time::date('Y-m-d H:i:s')); $this->processBillingAddress($this->getContributionContactID(), (string) $this->_contributorEmail); $this->_params['total_amount'] = CRM_Utils_Array::value('total_amount', $this->_params, CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType', $this->_memType, 'minimum_fee') ); - $customFieldsFormatted = CRM_Core_BAO_CustomField::postProcess($this->_params, + $customFieldsFormatted = CRM_Core_BAO_CustomField::postProcess($this->getSubmittedValues(), $this->getMembershipID(), 'Membership' ); @@ -676,7 +670,7 @@ protected function sendReceipt($membership) { } CRM_Core_BAO_UFGroup::getValues($this->_contactID, $customFields, $customValues, FALSE, $members); - $this->assign_by_ref('formValues', $this->_params); + $this->assign('formValues', $this->_params); $this->assign('membership_name', CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType', $membership->membership_type_id diff --git a/www/modules/civicrm/CRM/Member/Form/MembershipView.php b/www/modules/civicrm/CRM/Member/Form/MembershipView.php index 703f6f5e5..82fe0ef1f 100644 --- a/www/modules/civicrm/CRM/Member/Form/MembershipView.php +++ b/www/modules/civicrm/CRM/Member/Form/MembershipView.php @@ -247,7 +247,7 @@ public function preProcess() { if (!empty($membershipType['relationship_type_id']) && empty($values['owner_membership_id'])) { // display related contacts/membership block $this->assign('has_related', TRUE); - $this->assign('max_related', CRM_Utils_Array::value('max_related', $values, ts('Unlimited'))); + $this->assign('max_related', $values['max_related'] ?? ts('Unlimited')); // split the relations in 2 arrays based on direction $relTypeId = explode(CRM_Core_DAO::VALUE_SEPARATOR, $membershipType['relationship_type_id']); $relDirection = explode(CRM_Core_DAO::VALUE_SEPARATOR, $membershipType['relationship_direction']); diff --git a/www/modules/civicrm/CRM/Member/Import/Controller.php b/www/modules/civicrm/CRM/Member/Import/Controller.php deleted file mode 100644 index b647e940e..000000000 --- a/www/modules/civicrm/CRM/Member/Import/Controller.php +++ /dev/null @@ -1,41 +0,0 @@ -_stateMachine = new CRM_Import_StateMachine($this, $action); - - // create and instantiate the pages - $this->addPages($this->_stateMachine, $action); - - // add all the actions - $config = CRM_Core_Config::singleton(); - $this->addActions($config->uploadDir, ['uploadFile']); - } - -} diff --git a/www/modules/civicrm/CRM/Member/Info.php b/www/modules/civicrm/CRM/Member/Info.php index 653bff34c..d25b7a300 100644 --- a/www/modules/civicrm/CRM/Member/Info.php +++ b/www/modules/civicrm/CRM/Member/Info.php @@ -50,42 +50,22 @@ public function getInfo() { /** * @inheritDoc - * Provides permissions that are used by component. - * Needs to be implemented in component's information - * class. - * - * NOTE: if using conditionally permission return, - * implementation of $getAllUnconditionally is required. - * - * @param bool $getAllUnconditionally - * @param bool $descriptions - * Whether to return permission descriptions - * - * @return array|null - * collection of permissions, null if none */ - public function getPermissions($getAllUnconditionally = FALSE, $descriptions = FALSE) { + public function getPermissions(): array { $permissions = [ 'access CiviMember' => [ - ts('access CiviMember'), - ts('View memberships'), + 'label' => ts('access CiviMember'), + 'description' => ts('View memberships'), ], 'edit memberships' => [ - ts('edit memberships'), - ts('Create and update memberships'), + 'label' => ts('edit memberships'), + 'description' => ts('Create and update memberships'), ], 'delete in CiviMember' => [ - ts('delete in CiviMember'), - ts('Delete memberships'), + 'label' => ts('delete in CiviMember'), + 'description' => ts('Delete memberships'), ], ]; - - if (!$descriptions) { - foreach ($permissions as $name => $attr) { - $permissions[$name] = array_shift($attr); - } - } - return $permissions; } diff --git a/www/modules/civicrm/CRM/Member/Page/RecurringContributions.php b/www/modules/civicrm/CRM/Member/Page/RecurringContributions.php index 5bd340fd9..1f12d0542 100644 --- a/www/modules/civicrm/CRM/Member/Page/RecurringContributions.php +++ b/www/modules/civicrm/CRM/Member/Page/RecurringContributions.php @@ -116,7 +116,7 @@ private function setActionsForRecurringContribution(int $recurID, &$recurringCon $recurringContribution['is_active'] = ($recurringContribution['contribution_status_id'] != 3); if ($recurringContribution['is_active']) { - $details = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($recurringContribution['id'], 'recur'); + $details = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($recurringContribution['id']); $hideUpdate = $details->membership_id & $details->auto_renew; if ($hideUpdate) { diff --git a/www/modules/civicrm/CRM/Member/Page/Tab.php b/www/modules/civicrm/CRM/Member/Page/Tab.php index 7b1eb4de5..977d6b1b9 100644 --- a/www/modules/civicrm/CRM/Member/Page/Tab.php +++ b/www/modules/civicrm/CRM/Member/Page/Tab.php @@ -245,13 +245,6 @@ public function browse() { $displayName = CRM_Contact_BAO_Contact::displayName($this->_contactId); $this->assign('displayName', $displayName); $this->ajaxResponse['tabCount'] = CRM_Contact_BAO_Contact::getCountComponent('membership', $this->_contactId); - // Refresh other tabs with related data - $this->ajaxResponse['updateTabs'] = [ - '#tab_activity' => CRM_Contact_BAO_Contact::getCountComponent('activity', $this->_contactId), - ]; - if (CRM_Core_Permission::access('CiviContribute')) { - $this->ajaxResponse['updateTabs']['#tab_contribute'] = CRM_Contact_BAO_Contact::getCountComponent('contribution', $this->_contactId); - } } } @@ -523,6 +516,8 @@ public static function &links( 'url' => 'civicrm/contact/view/membership', 'qs' => 'action=view&reset=1&cid=%%cid%%&id=%%id%%&context=membership&selectedChild=member', 'title' => ts('View Membership'), + // The constants are a bit backward - VIEW comes after UPDATE + 'weight' => 2, ], ]; } @@ -534,24 +529,29 @@ public static function &links( 'url' => 'civicrm/contact/view/membership', 'qs' => 'action=update&reset=1&cid=%%cid%%&id=%%id%%&context=membership&selectedChild=member', 'title' => ts('Edit Membership'), + // The constants are a bit backward - VIEW comes after UPDATE + 'weight' => 4, ], CRM_Core_Action::RENEW => [ 'name' => ts('Renew'), 'url' => 'civicrm/contact/view/membership', 'qs' => 'action=renew&reset=1&cid=%%cid%%&id=%%id%%&context=membership&selectedChild=member', 'title' => ts('Renew Membership'), + 'weight' => CRM_Core_Action::RENEW, ], CRM_Core_Action::FOLLOWUP => [ 'name' => ts('Renew-Credit Card'), 'url' => 'civicrm/contact/view/membership', 'qs' => 'action=renew&reset=1&cid=%%cid%%&id=%%id%%&context=membership&selectedChild=member&mode=live', 'title' => ts('Renew Membership Using Credit Card'), + 'weight' => CRM_Core_Action::FOLLOWUP, ], CRM_Core_Action::DELETE => [ 'name' => ts('Delete'), 'url' => 'civicrm/contact/view/membership', 'qs' => 'action=delete&reset=1&cid=%%cid%%&id=%%id%%&context=membership&selectedChild=member', 'title' => ts('Delete Membership'), + 'weight' => CRM_Core_Action::DELETE, ], ]; if (!$isPaymentProcessor || !$accessContribution) { @@ -570,6 +570,7 @@ public static function &links( 'qs' => 'reset=1&cid=%%cid%%&mid=%%id%%&context=membership&selectedChild=member', 'title' => ts('Cancel Auto Renew Subscription'), 'extra' => 'onclick = "if (confirm(\'' . $cancelMessage . '\') ) { return true; else return false;}"', + 'weight' => CRM_Core_Action::DISABLE, ]; } elseif (isset(self::$_links['all'][CRM_Core_Action::DISABLE])) { @@ -582,6 +583,7 @@ public static function &links( 'url' => 'civicrm/contribute/updatebilling', 'qs' => 'reset=1&cid=%%cid%%&mid=%%id%%&context=membership&selectedChild=member', 'title' => ts('Change Billing Details'), + 'weight' => CRM_Core_Action::MAP, ]; } elseif (isset(self::$_links['all'][CRM_Core_Action::MAP])) { diff --git a/www/modules/civicrm/CRM/Member/Selector/Search.php b/www/modules/civicrm/CRM/Member/Selector/Search.php index 6de5c53de..1aff108f0 100644 --- a/www/modules/civicrm/CRM/Member/Selector/Search.php +++ b/www/modules/civicrm/CRM/Member/Selector/Search.php @@ -440,7 +440,7 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $row['auto_renew'] = 0; } - $row['contact_type'] = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ? $result->contact_sub_type : $result->contact_type, FALSE, $result->contact_id + $row['contact_type'] = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ?: $result->contact_type, FALSE, $result->contact_id ); $rows[] = $row; diff --git a/www/modules/civicrm/CRM/Member/xml/Menu/Member.xml b/www/modules/civicrm/CRM/Member/xml/Menu/Member.xml index c8b0dae7f..614a74ab1 100644 --- a/www/modules/civicrm/CRM/Member/xml/Menu/Member.xml +++ b/www/modules/civicrm/CRM/Member/xml/Menu/Member.xml @@ -66,10 +66,11 @@ 710
- civicrm/member/import + civicrm/import/membership Import Memberships - CRM_Member_Import_Controller + CRM_Import_Controller access CiviMember,edit memberships + entity_prefix=Member 1 720 diff --git a/www/modules/civicrm/CRM/PCP/BAO/PCP.php b/www/modules/civicrm/CRM/PCP/BAO/PCP.php index 82a1da40a..6bc8112a6 100644 --- a/www/modules/civicrm/CRM/PCP/BAO/PCP.php +++ b/www/modules/civicrm/CRM/PCP/BAO/PCP.php @@ -968,4 +968,28 @@ protected static function getPcpTitle(string $component, int $id) { return CRM_Core_PseudoConstant::getLabel('CRM_Event_BAO_Participant', 'event_id', $id); } + /** + * Possible values for the page_type field, used for dynamic foreign key lookups + * + * This is a non-standard dfk. Normally the columns would be named "entity_table" & "entity_id", + * and normally the `entity_table` field would contain a table name like "civicrm_contribution" + * instead of an arbitrary string like 'contribute'. + * + * @return array + */ + public static function pageTypeOptions(): array { + return [ + [ + 'id' => 'contribute', + 'name' => 'ContributionPage', + 'label' => ts('Contributions'), + ], + [ + 'id' => 'event', + 'name' => 'Event', + 'label' => ts('Events'), + ], + ]; + } + } diff --git a/www/modules/civicrm/CRM/PCP/DAO/PCP.php b/www/modules/civicrm/CRM/PCP/DAO/PCP.php index eee6a7bc0..3bf87d01f 100644 --- a/www/modules/civicrm/CRM/PCP/DAO/PCP.php +++ b/www/modules/civicrm/CRM/PCP/DAO/PCP.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/PCP/PCP.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:be838855e518a084a4da2a98fe52480d) + * (GenCodeChecksum:c6e35776a99657306b28b5f8477d5690) */ /** @@ -374,6 +374,8 @@ public static function &fields() { 'entity' => 'PCP', 'bao' => 'CRM_PCP_BAO_PCP', 'localizable' => 0, + 'DFKEntityColumn' => 'page_type', + 'FKColumnName' => 'id', 'add' => '4.1', ], 'page_type' => [ @@ -398,6 +400,9 @@ public static function &fields() { 'html' => [ 'type' => 'Select', ], + 'pseudoconstant' => [ + 'callback' => 'CRM_PCP_BAO_PCP::pageTypeOptions', + ], 'add' => '2.2', ], 'pcp_block_id' => [ diff --git a/www/modules/civicrm/CRM/PCP/DAO/PCPBlock.php b/www/modules/civicrm/CRM/PCP/DAO/PCPBlock.php index ebf84f67a..47b433935 100644 --- a/www/modules/civicrm/CRM/PCP/DAO/PCPBlock.php +++ b/www/modules/civicrm/CRM/PCP/DAO/PCPBlock.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/PCP/PCPBlock.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:aa66cc545cf04e5ea640df960af2ddea) + * (GenCodeChecksum:458fa1443a7d669b553818b1770eba1d) */ /** diff --git a/www/modules/civicrm/CRM/PCP/Form/Campaign.php b/www/modules/civicrm/CRM/PCP/Form/Campaign.php index 59fc07b62..cad46a7b6 100644 --- a/www/modules/civicrm/CRM/PCP/Form/Campaign.php +++ b/www/modules/civicrm/CRM/PCP/Form/Campaign.php @@ -122,7 +122,7 @@ public function buildQuickForm() { $owner_notification_option = CRM_Core_DAO::getFieldValue('CRM_PCP_BAO_PCPBlock', $pcpInfo['pcp_block_id'], 'owner_notify_id'); } else { - $owner_notification_option = CRM_PCP_BAO_PCP::getOwnerNotificationId($this->controller->get('component_page_id'), $this->_component ? $this->_component : 'contribute'); + $owner_notification_option = CRM_PCP_BAO_PCP::getOwnerNotificationId($this->controller->get('component_page_id'), $this->_component ?: 'contribute'); } if ($owner_notification_option == CRM_Core_PseudoConstant::getKey('CRM_PCP_BAO_PCPBlock', 'owner_notify_id', 'owner_chooses')) { $this->assign('owner_notification_option', TRUE); @@ -194,7 +194,7 @@ public function postProcess() { $params['title'] = $params['pcp_title']; $params['intro_text'] = $params['pcp_intro_text']; $params['contact_id'] = $contactID; - $params['page_id'] = $this->get('component_page_id') ? $this->get('component_page_id') : $this->_contriPageId; + $params['page_id'] = $this->get('component_page_id') ?: $this->_contriPageId; $params['page_type'] = $this->_component; // since we are allowing html input from the user diff --git a/www/modules/civicrm/CRM/PCP/Page/PCP.php b/www/modules/civicrm/CRM/PCP/Page/PCP.php index e4523a346..ef0c58781 100644 --- a/www/modules/civicrm/CRM/PCP/Page/PCP.php +++ b/www/modules/civicrm/CRM/PCP/Page/PCP.php @@ -238,7 +238,7 @@ public function browse($action = NULL) { $pages['event'][$epages->id]['end_date'] = $epages->registration_end_date; } - $params = $this->get('params') ? $this->get('params') : []; + $params = $this->get('params') ?: []; $title = '1'; if ($this->_sortByCharacter !== NULL) { diff --git a/www/modules/civicrm/CRM/PCP/StateMachine/PCP.php b/www/modules/civicrm/CRM/PCP/StateMachine/PCP.php index 1949f9228..07975f07f 100644 --- a/www/modules/civicrm/CRM/PCP/StateMachine/PCP.php +++ b/www/modules/civicrm/CRM/PCP/StateMachine/PCP.php @@ -16,25 +16,19 @@ */ /** - * State machine for managing different states of the Import process. - * + * State machine for managing different pages while setting up PCP flow. */ class CRM_PCP_StateMachine_PCP extends CRM_Core_StateMachine { /** * Class constructor. * - * @param object $controller - * @param \const|int $action - * - * @internal param \CRM_Contact_Import_Controller $object - * @return \CRM_PCP_StateMachine_PCP CRM_Contact_Import_StateMachine + * @param CRM_PCP_Controller_PCP $controller + * @param int $action */ public function __construct($controller, $action = CRM_Core_Action::NONE) { parent::__construct($controller, $action); - - $session = CRM_Core_Session::singleton(); - $session->set('singleForm', FALSE); + CRM_Core_Session::singleton()->set('singleForm', FALSE); $this->_pages = [ 'CRM_PCP_Form_PCPAccount' => NULL, diff --git a/www/modules/civicrm/CRM/Pledge/DAO/Pledge.php b/www/modules/civicrm/CRM/Pledge/DAO/Pledge.php index d658126d7..1d4d2d6c4 100644 --- a/www/modules/civicrm/CRM/Pledge/DAO/Pledge.php +++ b/www/modules/civicrm/CRM/Pledge/DAO/Pledge.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Pledge/Pledge.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:0e5accbafc151eeed6b7a5de6d194e84) + * (GenCodeChecksum:95834e2210d6808f89b9afd8e3a3f601) */ /** diff --git a/www/modules/civicrm/CRM/Pledge/DAO/PledgeBlock.php b/www/modules/civicrm/CRM/Pledge/DAO/PledgeBlock.php index bf17861d1..a472c5dd2 100644 --- a/www/modules/civicrm/CRM/Pledge/DAO/PledgeBlock.php +++ b/www/modules/civicrm/CRM/Pledge/DAO/PledgeBlock.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Pledge/PledgeBlock.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:f085d7cb178f9fcb0441c9229e317ec5) + * (GenCodeChecksum:91fae667e945aaf73143425e96c0b132) */ /** diff --git a/www/modules/civicrm/CRM/Pledge/DAO/PledgePayment.php b/www/modules/civicrm/CRM/Pledge/DAO/PledgePayment.php index a290fac0e..d3b7d214a 100644 --- a/www/modules/civicrm/CRM/Pledge/DAO/PledgePayment.php +++ b/www/modules/civicrm/CRM/Pledge/DAO/PledgePayment.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Pledge/PledgePayment.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:dd7ee360635e8fd3f8cfc484378f67b2) + * (GenCodeChecksum:7170b6632cf8cc041f87119f41b3b010) */ /** diff --git a/www/modules/civicrm/CRM/Pledge/Form/Pledge.php b/www/modules/civicrm/CRM/Pledge/Form/Pledge.php index ab873baa7..e39aa8672 100644 --- a/www/modules/civicrm/CRM/Pledge/Form/Pledge.php +++ b/www/modules/civicrm/CRM/Pledge/Form/Pledge.php @@ -22,6 +22,8 @@ */ class CRM_Pledge_Form_Pledge extends CRM_Core_Form { use CRM_Contact_Form_ContactFormTrait; + use CRM_Custom_Form_CustomDataTrait; + use CRM_Pledge_Form_PledgeFormTrait; public $_action; @@ -29,6 +31,8 @@ class CRM_Pledge_Form_Pledge extends CRM_Core_Form { * The id of the pledge that we are processing. * * @var int + * + * @internal */ public $_id; @@ -67,7 +71,6 @@ public function preProcess(): void { $this->_action = CRM_Utils_Request::retrieve('action', 'String', $this, FALSE, 'add' ); - $this->_id = CRM_Utils_Request::retrieve('id', 'Positive', $this); $this->setContext(); // check for action permissions. if (!CRM_Core_Permission::checkActionPermission('CiviPledge', $this->_action)) { @@ -82,15 +85,12 @@ public function preProcess(): void { $this->setPageTitle(ts('Pledge')); - // build custom data - CRM_Custom_Form_CustomData::preProcess($this, NULL, NULL, 1, 'Pledge', $this->_id); $this->_values = []; // current pledge id - if ($this->_id) { - $params = ['id' => $this->_id]; + if ($this->getPledgeID()) { + $params = ['id' => $this->getPledgeID()]; CRM_Pledge_BAO_Pledge::getValues($params, $this->_values); - - $this->_isPending = !CRM_Pledge_BAO_Pledge::pledgeHasFinancialTransactions($this->_id, CRM_Utils_Array::value('status_id', $this->_values)); + $this->_isPending = !CRM_Pledge_BAO_Pledge::pledgeHasFinancialTransactions($this->getPledgeID(), $this->getPledgeValue('status_id')); } // get the pledge frequency units. @@ -114,7 +114,7 @@ public function setDefaultValues(): array { $this->assign('is_test', TRUE); } - if ($this->_id) { + if ($this->getPledgeID()) { // check is this pledge pending. // fix the display of the monetary value, CRM-4038. if ($this->_isPending) { @@ -162,9 +162,6 @@ public function setDefaultValues(): array { )); $this->assign('email', $this->getContactValue('email_primary.email')); - // custom data set defaults - $defaults += CRM_Custom_Form_CustomData::setDefaultValues($this); - return $defaults; } @@ -194,7 +191,7 @@ public function buildQuickForm(): void { if ($this->getContext() !== 'standalone') { $contactField->freeze(); } - + $this->assign('pledgeID', $this->getPledgeID()); $formType = CRM_Utils_Request::retrieveValue('formType', 'String'); $allPanes[ts('Payment Reminders')] = [ @@ -336,7 +333,15 @@ public function buildQuickForm(): void { $this->assign('outBound_option', $mailingInfo['outBound_option']); // build custom data - CRM_Custom_Form_CustomData::buildQuickForm($this); + if ($this->isSubmitted()) { + // The custom data fields are added to the form by an ajax form. + // However, if they are not present in the element index they will + // not be available from `$this->getSubmittedValue()` in post process. + // We do not have to set defaults or otherwise render - just add to the element index. + $this->addCustomDataFieldsToForm('Pledge', array_filter([ + 'id' => $this->getPledgeID(), + ])); + } // make this form an upload since we dont know if the custom data injected dynamically // is of type file etc $uploadNames = $this->get( 'uploadNames' ); @@ -412,7 +417,7 @@ public static function formRule($fields, $files, $self) { */ public function postProcess(): void { if ($this->_action & CRM_Core_Action::DELETE) { - CRM_Pledge_BAO_Pledge::deletePledge($this->_id); + CRM_Pledge_BAO_Pledge::deletePledge($this->getPledgeID()); return; } @@ -443,13 +448,13 @@ public function postProcess(): void { } // format amount - $params['amount'] = CRM_Utils_Rule::cleanMoney(CRM_Utils_Array::value('amount', $formValues)); + $params['amount'] = $this->getSubmittedValue('amount'); $params['currency'] = $formValues['currency'] ?? NULL; $params['original_installment_amount'] = ($params['amount'] / $params['installments']); $dates = ['create_date', 'start_date', 'acknowledge_date', 'cancel_date']; foreach ($dates as $d) { - if ($this->_id && (!$this->_isPending) && !empty($this->_values[$d])) { + if ($this->getPledgeID() && (!$this->_isPending) && !empty($this->_values[$d])) { if ($d === 'start_date') { $params['scheduled_date'] = CRM_Utils_Date::processDate($this->_values[$d]); } @@ -472,20 +477,16 @@ public function postProcess(): void { // assign id only in update mode if ($this->_action & CRM_Core_Action::UPDATE) { - $params['id'] = $this->_id; + $params['id'] = $this->getPledgeID(); } $params['contact_id'] = $this->_contactID; // format custom data - if (!empty($formValues['hidden_custom'])) { - $params['hidden_custom'] = 1; - - $params['custom'] = CRM_Core_BAO_CustomField::postProcess($formValues, - $this->_id, - 'Pledge' - ); - } + $params['custom'] = CRM_Core_BAO_CustomField::postProcess($this->getSubmittedValues(), + $this->getPledgeID(), + 'Pledge' + ); // handle pending pledge. $params['is_pledge_pending'] = $this->_isPending; @@ -564,6 +565,13 @@ public function postProcess(): void { $session->replaceUserContext(CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$this->_contactID}&selectedChild=pledge" )); + // Refresh other tabs with related data + $this->ajaxResponse['updateTabs'] = [ + '#tab_activity' => TRUE, + ]; + if (CRM_Core_Permission::access('CiviContribute')) { + $this->ajaxResponse['updateTabs']['#tab_contribute'] = CRM_Contact_BAO_Contact::getCountComponent('contribution', $this->_contactID); + } } } elseif ($buttonName === $this->getButtonName('upload', 'new')) { @@ -573,4 +581,19 @@ public function postProcess(): void { } } + /** + * Get the pledge ID. + * + * @api supported for external use. + * + * @return int|null + * @throws \CRM_Core_Exception + */ + public function getPledgeID(): ?int { + if (!isset($this->_id)) { + $this->_id = CRM_Utils_Request::retrieve('id', 'Positive', $this); + } + return $this->_id ? (int) $this->_id : NULL; + } + } diff --git a/www/modules/civicrm/CRM/Pledge/Form/PledgeFormTrait.php b/www/modules/civicrm/CRM/Pledge/Form/PledgeFormTrait.php new file mode 100644 index 000000000..8f25befc1 --- /dev/null +++ b/www/modules/civicrm/CRM/Pledge/Form/PledgeFormTrait.php @@ -0,0 +1,55 @@ +isDefined('Pledge')) { + return $this->lookup('Pledge', $fieldName); + } + $id = $this->getPledgeID(); + if ($id) { + $this->define('Pledge', 'Pledge', ['id' => $id]); + return $this->lookup('Pledge', $fieldName); + } + return NULL; + } + +} diff --git a/www/modules/civicrm/CRM/Pledge/Form/PledgeView.php b/www/modules/civicrm/CRM/Pledge/Form/PledgeView.php index ed1e74e4b..2ab8cbf66 100644 --- a/www/modules/civicrm/CRM/Pledge/Form/PledgeView.php +++ b/www/modules/civicrm/CRM/Pledge/Form/PledgeView.php @@ -19,102 +19,93 @@ * This class generates form components for Pledge */ class CRM_Pledge_Form_PledgeView extends CRM_Core_Form { + use CRM_Pledge_Form_PledgeFormTrait; /** * Set variables up before form is built. + * + * @throws \CRM_Core_Exception */ - public function preProcess() { - - $values = $ids = []; - $params = ['id' => $this->get('id')]; - CRM_Pledge_BAO_Pledge::getValues($params, - $values, - $ids - ); - - $values['frequencyUnit'] = ts('%1(s)', [1 => $values['frequency_unit']]); - - if (isset($values["honor_contact_id"]) && $values["honor_contact_id"]) { - $sql = "SELECT display_name FROM civicrm_contact WHERE id = " . $values["honor_contact_id"]; - $dao = new CRM_Core_DAO(); - $dao->query($sql); - if ($dao->fetch()) { - $url = CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid=$values[honor_contact_id]"); - $values["honor_display"] = "" . $dao->display_name . ""; - } - $honor = CRM_Core_PseudoConstant::get('CRM_Pledge_DAO_Pledge', 'honor_type_id'); - $values['honor_type'] = $honor[$values['honor_type_id']]; - } + public function preProcess(): void { + $id = $this->getPledgeID(); + $contactID = $this->getPledgeValue('contact_id'); + $params = ['id' => $this->getPledgeID()]; // handle custom data. $groupTree = CRM_Core_BAO_CustomGroup::getTree('Pledge', NULL, $params['id'], NULL, [], NULL, TRUE, NULL, FALSE, CRM_Core_Permission::VIEW); CRM_Core_BAO_CustomGroup::buildCustomDataView($this, $groupTree, FALSE, NULL, NULL, NULL, $params['id']); - if (!empty($values['contribution_page_id'])) { - $values['contribution_page'] = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_ContributionPage', $values['contribution_page_id'], 'title'); - } - - $values['financial_type'] = CRM_Utils_Array::value($values['financial_type_id'], CRM_Contribute_PseudoConstant::financialType()); + $displayName = $this->getPledgeValue('contact_id.display_name'); - if ($values['status_id']) { - $values['pledge_status'] = CRM_Core_PseudoConstant::getLabel('CRM_Pledge_BAO_Pledge', 'status_id', $values['status_id']); + // Check if this is default domain contact CRM-10482 + if (CRM_Contact_BAO_Contact::checkDomainContact($contactID)) { + $displayName .= ' (' . ts('default organization') . ')'; } + // omitting contactImage from title for now since the summary overlay css doesn't work outside of our crm-container + $this->setTitle(ts('View Pledge by') . ' ' . $displayName); + $this->assign('displayName', $displayName); + + $title = $displayName . + ' - (' . ts('Pledged') . ' ' . CRM_Utils_Money::format($this->getPledgeValue('pledge_amount')) . + ' - ' . $this->getPledgeValue('financial_type_id:label') . ')'; $url = CRM_Utils_System::url('civicrm/contact/view/pledge', - "action=view&reset=1&id={$values['id']}&cid={$values['contact_id']}&context=home" + "action=view&reset=1&id={$id}&cid={$contactID}&context=home" ); $recentOther = []; if (CRM_Core_Permission::checkActionPermission('CiviPledge', CRM_Core_Action::UPDATE)) { $recentOther['editUrl'] = CRM_Utils_System::url('civicrm/contact/view/pledge', - "action=update&reset=1&id={$values['id']}&cid={$values['contact_id']}&context=home" + "action=update&reset=1&id={$id}&cid={$contactID}&context=home" ); } if (CRM_Core_Permission::checkActionPermission('CiviPledge', CRM_Core_Action::DELETE)) { $recentOther['deleteUrl'] = CRM_Utils_System::url('civicrm/contact/view/pledge', - "action=delete&reset=1&id={$values['id']}&cid={$values['contact_id']}&context=home" + "action=delete&reset=1&id={$id}&cid={$contactID}&context=home" ); } - - $displayName = CRM_Contact_BAO_Contact::displayName($values['contact_id']); - $this->assign('displayName', $displayName); - - $title = $displayName . - ' - (' . ts('Pledged') . ' ' . CRM_Utils_Money::format($values['pledge_amount']) . - ' - ' . $values['financial_type'] . ')'; - // add Pledge to Recent Items CRM_Utils_Recent::add($title, $url, - $values['id'], + $this->getPledgeID(), 'Pledge', - $values['contact_id'], + $this->getPledgeValue('contact_id'), NULL, $recentOther ); - // Check if this is default domain contact CRM-10482 - if (CRM_Contact_BAO_Contact::checkDomainContact($values['contact_id'])) { - $displayName .= ' (' . ts('default organization') . ')'; - } - // omitting contactImage from title for now since the summary overlay css doesn't work outside of our crm-container - $this->setTitle(ts('View Pledge by') . ' ' . $displayName); - - // do check for campaigns - $campaignId = $values['campaign_id'] ?? NULL; - if ($campaignId) { - $campaigns = CRM_Campaign_BAO_Campaign::getCampaigns($campaignId); - $values['campaign'] = $campaigns[$campaignId]; - } - - $this->assign($values); + $this->assign('campaign', $this->getPledgeValue('campaign_id:label')); + $currentInstallment = $this->getPledgeValue('amount') / $this->getPledgeValue('installments'); + $this->assign('currentInstallment', $currentInstallment); + $this->assign('originalPledgeAmount', $currentInstallment === $this->getPledgeValue('original_installment_amount') ? NULL : $this->getPledgeValue('installments') * $this->getPledgeValue('original_installment_amount')); + $this->assign('start_date', $this->getPledgeValue('start_date')); + $this->assign('create_date', $this->getPledgeValue('create_date')); + $this->assign('end_date', $this->getPledgeValue('end_date')); + $this->assign('cancel_date', $this->getPledgeValue('cancel_date')); + $this->assign('is_test', $this->getPledgeValue('is_test')); + $this->assign('acknowledge_date', $this->getPledgeValue('acknowledge_date')); + $this->assign('pledge_status', $this->getPledgeValue('status_id:label')); + $this->assign('pledge_status_name', $this->getPledgeValue('status_id:name')); + $this->assign('amount', $this->getPledgeValue('amount')); + $this->assign('currency', $this->getPledgeValue('currency')); + $this->assign('initial_reminder_day', $this->getPledgeValue('initial_reminder_day')); + $this->assign('additional_reminder_day', $this->getPledgeValue('additional_reminder_day')); + $this->assign('max_reminders', $this->getPledgeValue('max_reminders')); + $this->assign('installments', $this->getPledgeValue('installments')); + $this->assign('original_installment_amount', $this->getPledgeValue('original_installment_amount')); + $this->assign('frequency_interval', $this->getPledgeValue('frequency_interval')); + $this->assign('frequency_day', $this->getPledgeValue('frequency_day')); + $this->assign('contribution_page', $this->getPledgeValue('contribution_page_id.title')); + $this->assign('contact_id', $this->getPledgeValue('contact_id')); + $this->assign('financial_type', $this->getPledgeValue('financial_type_id:label')); + $this->assign('frequencyUnit', ts('%1(s)', [1 => $this->getPledgeValue('frequency_unit')])); } /** * Build the form object. */ - public function buildQuickForm() { + public function buildQuickForm(): void { $this->addButtons([ [ 'type' => 'next', @@ -125,4 +116,15 @@ public function buildQuickForm() { ]); } + /** + * Get id of Pledge being acted on. + * + * @api This function will not change in a minor release and is supported for + * use outside of core. This annotation / external support for properties + * is only given where there is specific test cover. + */ + public function getPledgeID(): int { + return (int) $this->get('id'); + } + } diff --git a/www/modules/civicrm/CRM/Pledge/Info.php b/www/modules/civicrm/CRM/Pledge/Info.php index 5b3c113e5..395a3d988 100644 --- a/www/modules/civicrm/CRM/Pledge/Info.php +++ b/www/modules/civicrm/CRM/Pledge/Info.php @@ -45,42 +45,22 @@ public function getInfo() { /** * @inheritDoc - * Provides permissions that are used by component. - * Needs to be implemented in component's information - * class. - * - * NOTE: if using conditionally permission return, - * implementation of $getAllUnconditionally is required. - * - * @param bool $getAllUnconditionally - * @param bool $descriptions - * Whether to return permission descriptions - * - * @return array|null - * collection of permissions, null if none */ - public function getPermissions($getAllUnconditionally = FALSE, $descriptions = FALSE) { + public function getPermissions(): array { $permissions = [ 'access CiviPledge' => [ - ts('access CiviPledge'), - ts('View pledges'), + 'label' => ts('access CiviPledge'), + 'description' => ts('View pledges'), ], 'edit pledges' => [ - ts('edit pledges'), - ts('Create and update pledges'), + 'label' => ts('edit pledges'), + 'description' => ts('Create and update pledges'), ], 'delete in CiviPledge' => [ - ts('delete in CiviPledge'), - ts('Delete pledges'), + 'label' => ts('delete in CiviPledge'), + 'description' => ts('Delete pledges'), ], ]; - - if (!$descriptions) { - foreach ($permissions as $name => $attr) { - $permissions[$name] = array_shift($attr); - } - } - return $permissions; } diff --git a/www/modules/civicrm/CRM/Pledge/Page/Tab.php b/www/modules/civicrm/CRM/Pledge/Page/Tab.php index 49d050e3b..653b056bd 100644 --- a/www/modules/civicrm/CRM/Pledge/Page/Tab.php +++ b/www/modules/civicrm/CRM/Pledge/Page/Tab.php @@ -35,11 +35,6 @@ public function browse() { $displayName = CRM_Contact_BAO_Contact::displayName($this->_contactId); $this->assign('displayName', $displayName); $this->ajaxResponse['tabCount'] = CRM_Contact_BAO_Contact::getCountComponent('pledge', $this->_contactId); - // Refresh other tabs with related data - $this->ajaxResponse['updateTabs'] = [ - '#tab_contribute' => CRM_Contact_BAO_Contact::getCountComponent('contribution', $this->_contactId), - '#tab_activity' => CRM_Contact_BAO_Contact::getCountComponent('activity', $this->_contactId), - ]; } } diff --git a/www/modules/civicrm/CRM/Pledge/Selector/Search.php b/www/modules/civicrm/CRM/Pledge/Selector/Search.php index 8dba0e803..ec2d71959 100644 --- a/www/modules/civicrm/CRM/Pledge/Selector/Search.php +++ b/www/modules/civicrm/CRM/Pledge/Selector/Search.php @@ -342,7 +342,7 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $result->pledge_id ); - $row['contact_type'] = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ? $result->contact_sub_type : $result->contact_type, FALSE, $result->contact_id + $row['contact_type'] = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ?: $result->contact_type, FALSE, $result->contact_id ); $rows[] = $row; } diff --git a/www/modules/civicrm/CRM/Price/BAO/LineItem.php b/www/modules/civicrm/CRM/Price/BAO/LineItem.php index 8338cd637..e281a4aa2 100644 --- a/www/modules/civicrm/CRM/Price/BAO/LineItem.php +++ b/www/modules/civicrm/CRM/Price/BAO/LineItem.php @@ -128,9 +128,11 @@ public static function getLineTotal($contributionId) { } /** - * Wrapper for line item retrieval when contribution ID is known. + * (quasi-deprecated) Wrapper for line item retrieval when contribution ID is known. * @param int $contributionID * + * @internal - apiv4 is recommended. + * * @return array */ public static function getLineItemsByContributionID($contributionID) { @@ -425,61 +427,6 @@ public static function processPriceSet($entityId, $lineItems, $contributionDetai } } - /** - * @param int $entityId - * @param string $entityTable - * @param $amount - * @param array $otherParams - */ - public static function syncLineItems($entityId, $entityTable, $amount, $otherParams = NULL) { - if (!$entityId || CRM_Utils_System::isNull($amount)) { - return; - } - - $from = " civicrm_line_item li - LEFT JOIN civicrm_price_field pf ON pf.id = li.price_field_id - LEFT JOIN civicrm_price_set ps ON ps.id = pf.price_set_id "; - - $set = " li.unit_price = %3, - li.line_total = %3 "; - - $where = " li.entity_id = %1 AND - li.entity_table = %2 "; - - $params = [ - 1 => [$entityId, 'Integer'], - 2 => [$entityTable, 'String'], - 3 => [$amount, 'Float'], - ]; - - if ($entityTable == 'civicrm_contribution') { - $entityName = 'default_contribution_amount'; - $where .= " AND ps.name = %4 "; - $params[4] = [$entityName, 'String']; - } - elseif ($entityTable == 'civicrm_participant') { - $from .= " - LEFT JOIN civicrm_price_set_entity cpse ON cpse.price_set_id = ps.id - LEFT JOIN civicrm_price_field_value cpfv ON cpfv.price_field_id = pf.id and cpfv.label = %4 "; - $set .= " ,li.label = %4, - li.price_field_value_id = cpfv.id "; - $where .= " AND cpse.entity_table = 'civicrm_event' AND cpse.entity_id = %5 "; - $amount = empty($amount) ? 0 : $amount; - $params += [ - 4 => [$otherParams['fee_label'], 'String'], - 5 => [$otherParams['event_id'], 'String'], - ]; - } - - $query = " - UPDATE $from - SET $set - WHERE $where - "; - - CRM_Core_DAO::executeQuery($query, $params); - } - /** * Build line items array. * @@ -677,7 +624,7 @@ public static function changeFeeSelections( $updatedAmount = CRM_Price_BAO_LineItem::getLineTotal($contributionId); } else { - $updatedAmount = CRM_Utils_Array::value('amount', $params, CRM_Utils_Array::value('total_amount', $params)); + $updatedAmount = $params['amount'] ?? $params['total_amount'] ?? NULL; } if (strlen($params['tax_amount']) != 0) { $taxAmount = $params['tax_amount']; diff --git a/www/modules/civicrm/CRM/Price/BAO/PriceField.php b/www/modules/civicrm/CRM/Price/BAO/PriceField.php index 38629f0f0..8680b5261 100644 --- a/www/modules/civicrm/CRM/Price/BAO/PriceField.php +++ b/www/modules/civicrm/CRM/Price/BAO/PriceField.php @@ -111,20 +111,20 @@ public static function create(&$params) { 'label' => trim($params['option_label'][$index]), 'name' => CRM_Utils_String::munge($params['option_label'][$index], '_', 64), 'amount' => trim($params['option_amount'][$index]), - 'count' => CRM_Utils_Array::value($index, CRM_Utils_Array::value('option_count', $params), NULL), - 'max_value' => CRM_Utils_Array::value($index, CRM_Utils_Array::value('option_max_value', $params), NULL), - 'description' => CRM_Utils_Array::value($index, CRM_Utils_Array::value('option_description', $params), NULL), - 'membership_type_id' => CRM_Utils_Array::value($index, CRM_Utils_Array::value('membership_type_id', $params), NULL), + 'count' => $params['option_count'][$index] ?? NULL, + 'max_value' => $params['option_max_value'][$index] ?? NULL, + 'description' => $params['option_description'][$index] ?? NULL, + 'membership_type_id' => $params['membership_type_id'][$index] ?? NULL, 'weight' => $params['option_weight'][$index], 'is_active' => 1, 'is_default' => !empty($defaultArray[$params['option_weight'][$index]]) ? $defaultArray[$params['option_weight'][$index]] : 0, 'membership_num_terms' => NULL, 'non_deductible_amount' => $params['non_deductible_amount'] ?? NULL, - 'visibility_id' => CRM_Utils_Array::value($index, CRM_Utils_Array::value('option_visibility_id', $params), self::getVisibilityOptionID('public')), + 'visibility_id' => $params['option_visibility_id'][$index] ?? self::getVisibilityOptionID('public'), ]; if ($options['membership_type_id']) { - $options['membership_num_terms'] = CRM_Utils_Array::value($index, CRM_Utils_Array::value('membership_num_terms', $params), 1); + $options['membership_num_terms'] = $params['membership_num_terms'][$index] ?? 1; $options['is_default'] = !empty($defaultArray[$params['membership_type_id'][$index]]) ? $defaultArray[$params['membership_type_id'][$index]] : 0; } @@ -783,7 +783,7 @@ public static function buildPriceOptionText($opt, $isDisplayAmounts, $valueField $taxAmount = $opt['tax_amount'] ?? NULL; if ($isDisplayAmounts) { $optionLabel = !empty($optionLabel) ? $optionLabel . ' - ' : ''; - if (isset($taxAmount) && $invoicing) { + if ($opt['tax_amount'] && $invoicing) { $optionLabel = $optionLabel . self::getTaxLabel($opt, $valueFieldName); } else { diff --git a/www/modules/civicrm/CRM/Price/BAO/PriceFieldValue.php b/www/modules/civicrm/CRM/Price/BAO/PriceFieldValue.php index e267621e0..b683619a5 100644 --- a/www/modules/civicrm/CRM/Price/BAO/PriceFieldValue.php +++ b/www/modules/civicrm/CRM/Price/BAO/PriceFieldValue.php @@ -73,7 +73,7 @@ public static function create(&$params, $ids = []) { if ($id) { $oldWeight = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $id, 'weight', 'id'); } - $fieldValues = ['price_field_id' => CRM_Utils_Array::value('price_field_id', $params, 0)]; + $fieldValues = ['price_field_id' => $params['price_field_id'] ?? 0]; $params['weight'] = CRM_Utils_Weight::updateOtherWeights('CRM_Price_DAO_PriceFieldValue', $oldWeight, $params['weight'], $fieldValues); } elseif (!$id) { diff --git a/www/modules/civicrm/CRM/Price/BAO/PriceSet.php b/www/modules/civicrm/CRM/Price/BAO/PriceSet.php index 662eca698..b870325ca 100644 --- a/www/modules/civicrm/CRM/Price/BAO/PriceSet.php +++ b/www/modules/civicrm/CRM/Price/BAO/PriceSet.php @@ -667,6 +667,7 @@ public static function getCachedPriceSetDetail(int $priceSetID): array { ->execute()->first(); $data['fields'] = (array) PriceField::get(FALSE) ->addWhere('price_set_id', '=', $priceSetID) + ->addWhere('is_active', '=', TRUE) ->addSelect('*', 'visibility_id:name') ->addOrderBy('weight', 'ASC') ->execute()->indexBy('id'); @@ -681,6 +682,7 @@ public static function getCachedPriceSetDetail(int $priceSetID): array { } $options = PriceFieldValue::get(FALSE) ->addWhere('price_field_id', 'IN', array_keys($data['fields'])) + ->addWhere('is_active', '=', TRUE) ->setSelect($select) ->addOrderBy('weight', 'ASC') ->execute(); @@ -709,6 +711,7 @@ public static function getCachedPriceSetDetail(int $priceSetID): array { * @deprecated since 5.68. Will be removed around 5.80. */ public static function buildPriceSet(&$form, $component = NULL, $validFieldsOnly = TRUE) { + CRM_Core_Error::deprecatedWarning('internal function'); $priceSetId = $form->get('priceSetId'); if (!$priceSetId) { return; diff --git a/www/modules/civicrm/CRM/Price/DAO/LineItem.php b/www/modules/civicrm/CRM/Price/DAO/LineItem.php index 68847eddd..f0bdbc237 100644 --- a/www/modules/civicrm/CRM/Price/DAO/LineItem.php +++ b/www/modules/civicrm/CRM/Price/DAO/LineItem.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Price/LineItem.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:4ed8cb9b05a2f47927b4fcfdefc6acc4) + * (GenCodeChecksum:3680ce678d999b96b3d6533393a8aa5a) */ /** diff --git a/www/modules/civicrm/CRM/Price/DAO/PriceField.php b/www/modules/civicrm/CRM/Price/DAO/PriceField.php index 4027b35e8..d18440688 100644 --- a/www/modules/civicrm/CRM/Price/DAO/PriceField.php +++ b/www/modules/civicrm/CRM/Price/DAO/PriceField.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Price/PriceField.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:3c89e8cae44de908cbe9946b74e8dead) + * (GenCodeChecksum:a025293005d5f886c5371e539ceaa75d) */ /** diff --git a/www/modules/civicrm/CRM/Price/DAO/PriceFieldValue.php b/www/modules/civicrm/CRM/Price/DAO/PriceFieldValue.php index 1fbe09752..9c92180c8 100644 --- a/www/modules/civicrm/CRM/Price/DAO/PriceFieldValue.php +++ b/www/modules/civicrm/CRM/Price/DAO/PriceFieldValue.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Price/PriceFieldValue.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:9fe8e00e503d24fc46fd8f0d94cf4b55) + * (GenCodeChecksum:2a91ab82b91ce2a5398dc3def85cc4cc) */ /** diff --git a/www/modules/civicrm/CRM/Price/DAO/PriceSet.php b/www/modules/civicrm/CRM/Price/DAO/PriceSet.php index ee3735f73..630cdcb7c 100644 --- a/www/modules/civicrm/CRM/Price/DAO/PriceSet.php +++ b/www/modules/civicrm/CRM/Price/DAO/PriceSet.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Price/PriceSet.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:92854c16878bd3b29650b6e2230255a9) + * (GenCodeChecksum:05175f4af5a44203932d2db0c7ee06cc) */ /** diff --git a/www/modules/civicrm/CRM/Price/DAO/PriceSetEntity.php b/www/modules/civicrm/CRM/Price/DAO/PriceSetEntity.php index d5c11dbbb..84bc2f900 100644 --- a/www/modules/civicrm/CRM/Price/DAO/PriceSetEntity.php +++ b/www/modules/civicrm/CRM/Price/DAO/PriceSetEntity.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Price/PriceSetEntity.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:f1affb16f087370afd28e790a9113d60) + * (GenCodeChecksum:3c70a3fe4a64b314aaed6a5cca01de82) */ /** diff --git a/www/modules/civicrm/CRM/Price/Form/Field.php b/www/modules/civicrm/CRM/Price/Form/Field.php index 844d49fa9..2fcdb1b17 100644 --- a/www/modules/civicrm/CRM/Price/Form/Field.php +++ b/www/modules/civicrm/CRM/Price/Form/Field.php @@ -122,7 +122,7 @@ public function setDefaultValues() { $this->_sid = $defaults['price_set_id']; // if text, retrieve price - if ($defaults['html_type'] == 'Text') { + if ($defaults['html_type'] === 'Text') { $isActive = $defaults['is_active']; $valueParams = ['price_field_id' => $this->getEntityId()]; @@ -188,18 +188,8 @@ public function buildQuickForm() { // Text box for Participant Count for a field // Financial Type - $financialType = CRM_Financial_BAO_FinancialType::getIncomeFinancialType(); - foreach ($financialType as $finTypeId => $type) { - if (CRM_Financial_BAO_FinancialType::isACLFinancialTypeStatus() - && !CRM_Core_Permission::check('add contributions of type ' . $type) - ) { - unset($financialType[$finTypeId]); - } - } - if (count($financialType)) { - $this->assign('financialType', $financialType); - } - + $financialTypes = CRM_Financial_BAO_FinancialType::getIncomeFinancialType(); + $this->assign('financialType', $financialTypes); //Visibility Type Options $visibilityType = CRM_Core_PseudoConstant::visibility(); $this->assign('visibilityType', $visibilityType); @@ -217,7 +207,7 @@ public function buildQuickForm() { $this->add('select', 'financial_type_id', ts('Financial Type'), - [' ' => ts('- select -')] + $financialType + [' ' => ts('- select -')] + $financialTypes ); $this->assign('useForMember', FALSE); @@ -285,7 +275,7 @@ public function buildQuickForm() { 'select', 'option_financial_type_id[' . $i . ']', ts('Financial Type'), - ['' => ts('- select -')] + $financialType + ['' => ts('- select -')] + $financialTypes ); if (in_array($eventComponentId, $this->_extendComponentId)) { // count @@ -403,7 +393,7 @@ public function buildQuickForm() { * @param array $files * @param self $form * - * @return array + * @return array|bool * if errors then list of errors to be posted back to the form, * true otherwise */ @@ -430,7 +420,7 @@ public static function formRule($fields, $files, $form) { if ((is_numeric($fields['count'] ?? '') && empty($fields['count']) ) && - (($fields['html_type'] ?? NULL) == 'Text') + (($fields['html_type'] ?? NULL) === 'Text') ) { $errors['count'] = ts('Participant Count must be greater than zero.'); } @@ -442,7 +432,7 @@ public static function formRule($fields, $files, $form) { } if ($form->_action & CRM_Core_Action::ADD) { - if ($fields['html_type'] != 'Text') { + if ($fields['html_type'] !== 'Text') { $countemptyrows = 0; $publicOptionCount = $_flagOption = $_rowError = 0; @@ -491,7 +481,7 @@ public static function formRule($fields, $files, $form) { $_flagOption = 1; } } - if (!$noLabel && !$noAmount && !empty($fields['option_financial_type_id']) && $fields['option_financial_type_id'][$index] == '' && $fields['html_type'] != 'Text') { + if (!$noLabel && !$noAmount && !empty($fields['option_financial_type_id']) && $fields['option_financial_type_id'][$index] == '' && $fields['html_type'] !== 'Text') { $errors["option_financial_type_id[{$index}]"] = ts('Financial Type is a Required field.'); } if ($noLabel && !$noAmount) { @@ -534,7 +524,7 @@ public static function formRule($fields, $files, $form) { } if (!empty($fields['option_visibility_id'][$index]) && (!$noLabel || !$noAmount)) { - if ($visibilityOptions[$fields['option_visibility_id'][$index]] == 'public') { + if ($visibilityOptions[$fields['option_visibility_id'][$index]] === 'public') { $publicOptionCount++; } } @@ -544,7 +534,7 @@ public static function formRule($fields, $files, $form) { if (!empty($memTypesIDS)) { // check for checkboxes allowing user to select multiple memberships from same membership organization - if ($fields['html_type'] == 'CheckBox') { + if ($fields['html_type'] === 'CheckBox') { $foundDuplicate = FALSE; $orgIds = []; foreach ($memTypesIDS as $key => $val) { @@ -587,7 +577,7 @@ public static function formRule($fields, $files, $form) { $_flagOption = 1; } - if ($visibilityOptions[$fields['visibility_id']] == 'public' && $publicOptionCount == 0) { + if ($visibilityOptions[$fields['visibility_id']] === 'public' && $publicOptionCount == 0) { $errors['visibility_id'] = ts('You have selected to make this field public but have not enabled any public price options. Please update your selections to include a public price option, or make this field admin visibility only.'); for ($index = 1; $index <= self::NUM_OPTION; $index++) { if (!empty($fields['option_label'][$index]) || !empty($fields['option_amount'][$index])) { @@ -596,7 +586,7 @@ public static function formRule($fields, $files, $form) { } } - if ($visibilityOptions[$fields['visibility_id']] == 'admin' && $publicOptionCount > 0) { + if ($visibilityOptions[$fields['visibility_id']] === 'admin' && $publicOptionCount > 0) { $errors['visibility_id'] = ts('Field with \'Admin\' visibility should only contain \'Admin\' options.'); for ($index = 1; $index <= self::NUM_OPTION; $index++) { @@ -604,7 +594,7 @@ public static function formRule($fields, $files, $form) { $isOptionSet = !empty($fields['option_label'][$index]) || !empty($fields['option_amount'][$index]); $currentOptionVisibility = $visibilityOptions[$fields['option_visibility_id'][$index]] ?? NULL; - if ($isOptionSet && $currentOptionVisibility == 'public') { + if ($isOptionSet && $currentOptionVisibility === 'public') { $errors["option_visibility_id[{$index}]"] = ts('\'Admin\' field should only have \'Admin\' visibility options.'); } } @@ -643,12 +633,7 @@ public function postProcess() { // store the submitted values in an array $params = $this->controller->exportValues('Field'); $params['id'] = $this->getEntityId(); - $priceField = $this->submit($params); - if (!is_a($priceField, 'CRM_Core_Error')) { - // Required by extensions implementing the postProcess hook (to get the ID of new entities) - $this->setEntityId($priceField->id); - CRM_Core_Session::setStatus(ts('Price Field \'%1\' has been saved.', [1 => $priceField->label]), ts('Saved'), 'success'); - } + $this->submit($params); $buttonName = $this->controller->getButtonName(); $session = CRM_Core_Session::singleton(); if ($buttonName == $this->getButtonName('next', 'new')) { @@ -699,17 +684,16 @@ public function submit($params) { $params['option_amount'] = [1 => $params['price']]; $params['option_label'] = [1 => $params['label']]; $params['option_count'] = [1 => $params['count']]; - $params['option_max_value'] = [1 => CRM_Utils_Array::value('max_value', $params)]; + $params['option_max_value'] = [1 => $params['max_value'] ?? NULL]; //$params['option_description'] = array( 1 => $params['description'] ); $params['option_weight'] = [1 => $params['weight']]; $params['option_financial_type_id'] = [1 => $params['financial_type_id']]; - $params['option_visibility_id'] = [1 => CRM_Utils_Array::value('visibility_id', $params)]; + $params['option_visibility_id'] = [1 => $params['visibility_id'] ?? NULL]; } $params['membership_num_terms'] = (!empty($params['membership_type_id'])) ? CRM_Utils_Array::value('membership_num_terms', $params, 1) : NULL; - $priceField = CRM_Price_BAO_PriceField::create($params); - return $priceField; + return CRM_Price_BAO_PriceField::create($params); } } diff --git a/www/modules/civicrm/CRM/Profile/Form.php b/www/modules/civicrm/CRM/Profile/Form.php index 9fcb0ed8a..4db369812 100644 --- a/www/modules/civicrm/CRM/Profile/Form.php +++ b/www/modules/civicrm/CRM/Profile/Form.php @@ -808,7 +808,7 @@ public function buildQuickForm(): void { // if we are a admin OR the same user OR acl-user with access to the profile // or we have checksum access to this contact (i.e. the user without a login) - CRM-5909 if ( - CRM_Core_Permission::check('administer users') || + CRM_Core_Permission::check('cms:administer users') || $this->_id == $this->_currentUserID || $this->_isPermissionedChecksum || in_array( diff --git a/www/modules/civicrm/CRM/Profile/Form/Edit.php b/www/modules/civicrm/CRM/Profile/Form/Edit.php index 29c9adff3..97c8dce66 100644 --- a/www/modules/civicrm/CRM/Profile/Form/Edit.php +++ b/www/modules/civicrm/CRM/Profile/Form/Edit.php @@ -201,7 +201,7 @@ public function buildQuickForm(): void { $cancelButtonValue = !empty($this->_ufGroup['cancel_button_text']) ? $this->_ufGroup['cancel_button_text'] : ts('Cancel'); $this->assign('cancelButtonText', $cancelButtonValue); - $this->assign('includeCancelButton', CRM_Utils_Array::value('add_cancel_button', $this->_ufGroup)); + $this->assign('includeCancelButton', $this->_ufGroup['add_cancel_button'] ?? FALSE); if (($this->_multiRecord & CRM_Core_Action::DELETE) && $this->_recordExists) { $this->_deleteButtonName = $this->getButtonName('upload', 'delete'); diff --git a/www/modules/civicrm/CRM/Profile/Page/Dynamic.php b/www/modules/civicrm/CRM/Profile/Page/Dynamic.php index d6009aa02..05d73ef1c 100644 --- a/www/modules/civicrm/CRM/Profile/Page/Dynamic.php +++ b/www/modules/civicrm/CRM/Profile/Page/Dynamic.php @@ -201,7 +201,7 @@ public function run() { $this->_isPermissionedChecksum = $allowPermission = FALSE; $permissionType = CRM_Core_Permission::VIEW; - if (CRM_Core_Permission::check('administer users') || CRM_Core_Permission::check('view all contacts') || CRM_Contact_BAO_Contact_Permission::allow($this->_id)) { + if (CRM_Core_Permission::check('cms:administer users') || CRM_Core_Permission::check('view all contacts') || CRM_Contact_BAO_Contact_Permission::allow($this->_id)) { $allowPermission = TRUE; } if ($this->_id != $userID) { @@ -339,8 +339,8 @@ public function run() { ]; } - $template->assign_by_ref('row', $values); - $template->assign_by_ref('profileFields', $profileFields); + $template->assign('row', $values); + $template->assign('profileFields', $profileFields); } $name = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', $this->_gid, 'name'); diff --git a/www/modules/civicrm/CRM/Profile/Page/Router.php b/www/modules/civicrm/CRM/Profile/Page/Router.php index 3dbd8491f..685ea8dc8 100644 --- a/www/modules/civicrm/CRM/Profile/Page/Router.php +++ b/www/modules/civicrm/CRM/Profile/Page/Router.php @@ -33,7 +33,7 @@ public function run($args = NULL) { return NULL; } - $secondArg = CRM_Utils_Array::value(2, $args, ''); + $secondArg = $args[2] ?? ''; if ($secondArg == 'map') { $controller = new CRM_Core_Controller_Simple( diff --git a/www/modules/civicrm/CRM/Profile/Selector/Listings.php b/www/modules/civicrm/CRM/Profile/Selector/Listings.php index c2ede9521..2f9aae2ab 100644 --- a/www/modules/civicrm/CRM/Profile/Selector/Listings.php +++ b/www/modules/civicrm/CRM/Profile/Selector/Listings.php @@ -563,7 +563,7 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL, $ex } $row = []; $empty = TRUE; - $row[] = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ? $result->contact_sub_type : $result->contact_type, + $row[] = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ?: $result->contact_type, FALSE, $result->contact_id, $showProfileOverlay diff --git a/www/modules/civicrm/CRM/Queue/DAO/Queue.php b/www/modules/civicrm/CRM/Queue/DAO/Queue.php index d86f829f7..e265c93ea 100644 --- a/www/modules/civicrm/CRM/Queue/DAO/Queue.php +++ b/www/modules/civicrm/CRM/Queue/DAO/Queue.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Queue/Queue.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:6ce8f7dd63f7ef581ed8df26d5839941) + * (GenCodeChecksum:2a737be504b5ea96df0dbd924aa0f6ce) */ /** diff --git a/www/modules/civicrm/CRM/Queue/DAO/QueueItem.php b/www/modules/civicrm/CRM/Queue/DAO/QueueItem.php index e6df67b0f..9ae9f1dfe 100644 --- a/www/modules/civicrm/CRM/Queue/DAO/QueueItem.php +++ b/www/modules/civicrm/CRM/Queue/DAO/QueueItem.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Queue/QueueItem.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:657f876ba4b87edaebee0e3eb1d346a1) + * (GenCodeChecksum:d2dfbdb6e314638d968e11df3bf98840) */ /** diff --git a/www/modules/civicrm/CRM/Queue/Menu.php b/www/modules/civicrm/CRM/Queue/Menu.php index c33e0e11e..6301c8ffc 100644 --- a/www/modules/civicrm/CRM/Queue/Menu.php +++ b/www/modules/civicrm/CRM/Queue/Menu.php @@ -17,8 +17,6 @@ * @copyright CiviCRM LLC https://civicrm.org/licensing */ -require_once 'CRM/Core/I18n.php'; - /** * Class CRM_Queue_Menu */ diff --git a/www/modules/civicrm/CRM/Report/DAO/ReportInstance.php b/www/modules/civicrm/CRM/Report/DAO/ReportInstance.php index 44b426f9e..a83b4f568 100644 --- a/www/modules/civicrm/CRM/Report/DAO/ReportInstance.php +++ b/www/modules/civicrm/CRM/Report/DAO/ReportInstance.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Report/ReportInstance.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:cec2ca039dc9ba4361ac9064b76e4a66) + * (GenCodeChecksum:c3cfd25abc2404853bcc64f6e488fa83) */ /** diff --git a/www/modules/civicrm/CRM/Report/Form.php b/www/modules/civicrm/CRM/Report/Form.php index 6b05e1edf..4abfdc039 100644 --- a/www/modules/civicrm/CRM/Report/Form.php +++ b/www/modules/civicrm/CRM/Report/Form.php @@ -820,7 +820,7 @@ public function preProcess() { $expFields = []; // higher preference to bao object - $daoOrBaoName = CRM_Utils_Array::value('bao', $table, CRM_Utils_Array::value('dao', $table)); + $daoOrBaoName = $table['bao'] ?? $table['dao'] ?? NULL; if ($daoOrBaoName) { if (method_exists($daoOrBaoName, 'exportableFields')) { @@ -1083,7 +1083,7 @@ public function setDefaultValues($freeze = TRUE) { ) { $order_by = [ 'column' => $fieldName, - 'order' => CRM_Utils_Array::value('default_order', $field, 'ASC'), + 'order' => $field['default_order'] ?? 'ASC', 'section' => $field['default_is_section'] ?? 0, ]; @@ -2525,7 +2525,7 @@ public function formatDisplay(&$rows, $pager = TRUE) { if ($chartEnabled) { $this->buildChart($rows); $this->_chartId = "{$this->_params['charts']}_" . - ($this->_id ? $this->_id : substr(get_class($this), 16)) . '_' . + ($this->_id ?: substr(get_class($this), 16)) . '_' . CRM_Core_Config::singleton()->userSystem->getSessionId(); $this->assign('chartId', $this->_chartId); } @@ -3366,7 +3366,7 @@ public function moveSummaryColumnsToTheRightHandSide() { */ public function doTemplateAssignment(&$rows) { $this->assign('columnHeaders', $this->_columnHeaders); - $this->assign_by_ref('rows', $rows); + $this->assign('rows', $rows); $this->assign('statistics', $this->statistics($rows)); } @@ -3762,7 +3762,7 @@ public function setPager($rowCount = NULL) { } $pager = new CRM_Utils_Pager($params); - $this->assign_by_ref('pager', $pager); + $this->assign('pager', $pager); $this->ajaxResponse['totalRows'] = $this->_rowsFound; } } @@ -4171,13 +4171,7 @@ public function customDataFrom($joinsForFiltersOnly = FALSE) { return; } $mapper = CRM_Core_BAO_CustomQuery::$extendsMap; - //CRM-18276 GROUP_CONCAT could be used with singleValueQuery and then exploded, - //but by default that truncates to 1024 characters, which causes errors with installs with lots of custom field sets - $customTables = []; - $customTablesDAO = CRM_Core_DAO::executeQuery("SELECT table_name FROM civicrm_custom_group"); - while ($customTablesDAO->fetch()) { - $customTables[] = $customTablesDAO->table_name; - } + $customTables = array_column(CRM_Core_BAO_CustomGroup::getAll(), 'table_name'); foreach ($this->_columns as $table => $prop) { if (in_array($table, $customTables)) { @@ -4186,7 +4180,7 @@ public function customDataFrom($joinsForFiltersOnly = FALSE) { if ((!$this->isFieldSelected($prop)) || ($joinsForFiltersOnly && !$this->isFieldFiltered($prop))) { continue; } - $baseJoin = CRM_Utils_Array::value($prop['extends'], $this->_customGroupExtendsJoin, "{$this->_aliases[$extendsTable]}.id"); + $baseJoin = $this->_customGroupExtendsJoin[$prop['extends']] ?? "{$this->_aliases[$extendsTable]}.id"; $customJoin = is_array($this->_customGroupJoin) ? $this->_customGroupJoin[$table] : $this->_customGroupJoin; $this->_from .= " @@ -4194,10 +4188,8 @@ public function customDataFrom($joinsForFiltersOnly = FALSE) { // handle for ContactReference if (array_key_exists('fields', $prop)) { foreach ($prop['fields'] as $fieldName => $field) { - if (($field['dataType'] ?? NULL) == - 'ContactReference' - ) { - $columnName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', CRM_Core_BAO_CustomField::getKeyID($fieldName), 'column_name'); + if (($field['dataType'] ?? NULL) === 'ContactReference') { + $columnName = CRM_Core_BAO_CustomField::getField(CRM_Core_BAO_CustomField::getKeyID($fieldName))['column_name']; $this->_from .= " LEFT JOIN civicrm_contact {$field['alias']} ON {$field['alias']}.id = {$this->_aliases[$table]}.{$columnName} "; } diff --git a/www/modules/civicrm/CRM/Report/Form/Contact/Detail.php b/www/modules/civicrm/CRM/Report/Form/Contact/Detail.php index b24927dba..072552bcb 100644 --- a/www/modules/civicrm/CRM/Report/Form/Contact/Detail.php +++ b/www/modules/civicrm/CRM/Report/Form/Contact/Detail.php @@ -843,8 +843,8 @@ public function postProcess(): void { unset($this->_columnHeadersComponent[$componentTitle][$id_header], $this->_columnHeadersComponent[$componentTitle][$contact_header]); } - $this->assign_by_ref('columnHeadersComponent', $this->_columnHeadersComponent); - $this->assign_by_ref('componentRows', $componentRows); + $this->assign('columnHeadersComponent', $this->_columnHeadersComponent); + $this->assign('componentRows', $componentRows); } $this->doTemplateAssignment($rows); diff --git a/www/modules/civicrm/CRM/Report/Form/Event/Income.php b/www/modules/civicrm/CRM/Report/Form/Event/Income.php index 9c21fb9f3..89f254fe0 100644 --- a/www/modules/civicrm/CRM/Report/Form/Event/Income.php +++ b/www/modules/civicrm/CRM/Report/Form/Event/Income.php @@ -76,7 +76,7 @@ public function buildEventReport($eventIDs) { $eventSummary[$eventDAO->event_id][ts('Registered Participant')] = "{$eventDAO->participant} (" . implode(', ', $this->getActiveParticipantStatuses()) . ")"; $currency[$eventDAO->event_id] = $eventDAO->currency; } - $this->assign_by_ref('summary', $eventSummary); + $this->assign('summary', $eventSummary); $activeParticipantClause = " AND civicrm_participant.status_id IN ( " . implode(',', array_keys($this->getActiveParticipantStatuses())) . " ) "; //Total Participant Registerd for the Event @@ -198,7 +198,7 @@ public function buildEventReport($eventIDs) { } $rows[ts('Payment Method')] = $instrumentRows; - $this->assign_by_ref('rows', $rows); + $this->assign('rows', $rows); if (!$this->_setVariable) { $this->_params['id_value'] = NULL; } @@ -254,7 +254,7 @@ public function setPager($rowCount = NULL) { ]; $pager = new CRM_Utils_Pager($params); - $this->assign_by_ref('pager', $pager); + $this->assign('pager', $pager); } /** diff --git a/www/modules/civicrm/CRM/Report/Form/Instance.php b/www/modules/civicrm/CRM/Report/Form/Instance.php index f51d354cd..4230e1506 100644 --- a/www/modules/civicrm/CRM/Report/Form/Instance.php +++ b/www/modules/civicrm/CRM/Report/Form/Instance.php @@ -134,7 +134,7 @@ public static function buildForm(&$form) { // navigation field $parentMenu = CRM_Core_BAO_Navigation::getNavigationList(); - $form->add('select', 'parent_id', ts('Parent Menu'), ['' => ts('- select -')] + $parentMenu, FALSE, ['class' => 'crm-select2 huge']); + $form->add('select', 'parent_id', ts('Parent Menu'), ['' => ts('- select -')] + $parentMenu); // For now we only providing drilldown for one primary detail report only. In future this could be multiple reports foreach ($form->_drilldownReport as $reportUrl => $drillLabel) { diff --git a/www/modules/civicrm/CRM/Report/Form/Mailing/Detail.php b/www/modules/civicrm/CRM/Report/Form/Mailing/Detail.php index 045e18b79..ab5ba6719 100644 --- a/www/modules/civicrm/CRM/Report/Form/Mailing/Detail.php +++ b/www/modules/civicrm/CRM/Report/Form/Mailing/Detail.php @@ -131,6 +131,10 @@ public function __construct() { 'title' => ts('Delivery Status'), 'default' => TRUE, ], + 'time_stamp' => [ + 'title' => ts('Delivery Date'), + 'default' => TRUE, + ], ], 'filters' => [ 'delivery_status' => [ @@ -144,6 +148,12 @@ public function __construct() { 'bounced' => 'Bounced', ], ], + 'time_stamp' => [ + 'name' => 'time_stamp', + 'title' => ts('Delivery Date'), + 'operatorType' => CRM_Report_Form::OP_DATE, + 'type' => CRM_Utils_Type::T_DATE, + ], ], 'grouping' => 'mailing-fields', ]; diff --git a/www/modules/civicrm/CRM/Report/Form/Membership/Summary.php b/www/modules/civicrm/CRM/Report/Form/Membership/Summary.php index 9ce592b5d..845ad80c0 100644 --- a/www/modules/civicrm/CRM/Report/Form/Membership/Summary.php +++ b/www/modules/civicrm/CRM/Report/Form/Membership/Summary.php @@ -314,8 +314,8 @@ public function postProcess() { } $this->formatDisplay($rows); - $this->assign_by_ref('columnHeaders', $this->_columnHeaders); - $this->assign_by_ref('rows', $rows); + $this->assign('columnHeaders', $this->_columnHeaders); + $this->assign('rows', $rows); $this->assign('statistics', $this->statistics($rows)); if (!empty($this->_params['charts'])) { diff --git a/www/modules/civicrm/CRM/Report/Info.php b/www/modules/civicrm/CRM/Report/Info.php index f86b06cf6..ba2705508 100644 --- a/www/modules/civicrm/CRM/Report/Info.php +++ b/www/modules/civicrm/CRM/Report/Info.php @@ -45,58 +45,38 @@ public function getInfo() { /** * @inheritDoc - * Provides permissions that are used by component. - * Needs to be implemented in component's information - * class. - * - * NOTE: if using conditionally permission return, - * implementation of $getAllUnconditionally is required. - * - * @param bool $getAllUnconditionally - * @param bool $descriptions - * Whether to return permission descriptions - * - * @return array|null - * collection of permissions, null if none */ - public function getPermissions($getAllUnconditionally = FALSE, $descriptions = FALSE) { + public function getPermissions(): array { $permissions = [ 'access CiviReport' => [ - ts('access CiviReport'), - ts('View reports'), + 'label' => ts('access CiviReport'), + 'description' => ts('View reports'), ], 'access Report Criteria' => [ - ts('access Report Criteria'), - ts('Change report search criteria'), + 'label' => ts('access Report Criteria'), + 'description' => ts('Change report search criteria'), ], 'save Report Criteria' => [ - ts('save Report Criteria'), - ts('Save report search criteria'), + 'label' => ts('save Report Criteria'), + 'description' => ts('Save report search criteria'), ], 'administer private reports' => [ - ts('administer private reports'), - ts('Edit all private reports'), + 'label' => ts('administer private reports'), + 'description' => ts('Edit all private reports'), ], 'administer reserved reports' => [ - ts('administer reserved reports'), - ts('Edit all reports that have been marked as reserved'), + 'label' => ts('administer reserved reports'), + 'description' => ts('Edit all reports that have been marked as reserved'), ], 'administer Reports' => [ - ts('administer Reports'), - ts('Manage report templates'), + 'label' => ts('administer Reports'), + 'description' => ts('Manage report templates'), ], 'view report sql' => [ - ts('view report sql'), - ts('View sql used in CiviReports'), + 'label' => ts('view report sql'), + 'description' => ts('View sql used in CiviReports'), ], ]; - - if (!$descriptions) { - foreach ($permissions as $name => $attr) { - $permissions[$name] = array_shift($attr); - } - } - return $permissions; } diff --git a/www/modules/civicrm/CRM/Report/Page/InstanceList.php b/www/modules/civicrm/CRM/Report/Page/InstanceList.php index 0d012d97b..0399d8769 100644 --- a/www/modules/civicrm/CRM/Report/Page/InstanceList.php +++ b/www/modules/civicrm/CRM/Report/Page/InstanceList.php @@ -241,16 +241,19 @@ protected function getActionLinks($instanceID, $className) { 'url' => CRM_Utils_System::url($urlCommon, 'reset=1&output=copy'), 'label' => ts('Save a Copy'), 'confirm_message' => NULL, + 'weight' => CRM_Core_Action::getWeight(\CRM_Core_Action::COPY), ], 'pdf' => [ 'url' => CRM_Utils_System::url($urlCommon, 'reset=1&force=1&output=pdf'), 'label' => ts('View as pdf'), 'confirm_message' => NULL, + 'weight' => CRM_Core_Action::getWeight(\CRM_Core_Action::EXPORT), ], 'print' => [ 'url' => CRM_Utils_System::url($urlCommon, 'reset=1&force=1&output=print'), 'label' => ts('Print report'), 'confirm_message' => NULL, + 'weight' => CRM_Core_Action::getWeight(\CRM_Core_Action::EXPORT), ], ]; // Hackery, Hackera, Hacker ahahahahahaha a super nasty hack. @@ -266,6 +269,7 @@ protected function getActionLinks($instanceID, $className) { 'url' => CRM_Utils_System::url($urlCommon, 'reset=1&force=1&output=csv'), 'label' => ts('Export to csv'), 'confirm_message' => NULL, + 'weight' => CRM_Core_Action::getWeight(\CRM_Core_Action::EXPORT), ]; } if (CRM_Core_Permission::check('administer Reports')) { @@ -273,6 +277,7 @@ protected function getActionLinks($instanceID, $className) { 'url' => CRM_Utils_System::url($urlCommon, 'reset=1&action=delete'), 'label' => ts('Delete report'), 'confirm_message' => ts('Are you sure you want delete this report? This action cannot be undone.'), + 'weight' => CRM_Core_Action::getWeight(\CRM_Core_Action::DELETE), ]; } CRM_Utils_Hook::links('view.report.links', diff --git a/www/modules/civicrm/CRM/Report/Utils/Report.php b/www/modules/civicrm/CRM/Report/Utils/Report.php index 33e5f7342..7a86860d2 100644 --- a/www/modules/civicrm/CRM/Report/Utils/Report.php +++ b/www/modules/civicrm/CRM/Report/Utils/Report.php @@ -236,7 +236,8 @@ public static function makeCsv(&$form, &$rows) { // Replace internal header names with friendly ones, where available. foreach ($columnHeaders as $header) { if (isset($form->_columnHeaders[$header])) { - $headers[] = '"' . html_entity_decode(strip_tags($form->_columnHeaders[$header]['title'])) . '"'; + $title = $form->_columnHeaders[$header]['title'] ?? ''; + $headers[] = '"' . html_entity_decode(strip_tags($title)) . '"'; } } // Add the headers. @@ -390,7 +391,7 @@ public static function processReport($params) { $_REQUEST['sendmail'] = CRM_Utils_Array::value('sendmail', $params, 1); // if cron is run from terminal --output is reserved, and therefore we would provide another name 'format' - $_REQUEST['output'] = CRM_Utils_Array::value('format', $params, CRM_Utils_Array::value('output', $params, 'pdf')); + $_REQUEST['output'] = $params['format'] ?? $params['output'] ?? 'pdf'; $_REQUEST['reset'] = CRM_Utils_Array::value('reset', $params, 1); $optionVal = self::getValueFromUrl($instanceId); diff --git a/www/modules/civicrm/CRM/SMS/BAO/Provider.php b/www/modules/civicrm/CRM/SMS/BAO/Provider.php index 72158e34c..c3762ad46 100644 --- a/www/modules/civicrm/CRM/SMS/BAO/Provider.php +++ b/www/modules/civicrm/CRM/SMS/BAO/Provider.php @@ -1,179 +1,2 @@ [CRM_Core_Config::domainID(), 'Positive']]); - } - - /** - * Retrieves the list of providers from the database. - * - * $selectArr array of coloumns to fetch - * $getActive boolean to get active providers - * - * @param null $selectArr - * @param null $filter - * @param bool $getActive - * @param string $orderBy - * - * @return array - */ - public static function getProviders($selectArr = NULL, $filter = NULL, $getActive = TRUE, $orderBy = 'id') { - - $providers = []; - $temp = []; - $dao = new CRM_SMS_DAO_Provider(); - if ($filter && !array_key_exists('is_active', $filter) && $getActive) { - $dao->is_active = 1; - } - if ($filter && is_array($filter)) { - foreach ($filter as $key => $value) { - $dao->$key = $value; - } - } - if ($selectArr && is_array($selectArr)) { - $select = implode(',', $selectArr); - $dao->selectAdd($select); - } - $dao->whereAdd("(domain_id = " . CRM_Core_Config::domainID() . " OR domain_id IS NULL)"); - $dao->orderBy($orderBy); - $dao->find(); - while ($dao->fetch()) { - CRM_Core_DAO::storeValues($dao, $temp); - $providers[$dao->id] = $temp; - } - return $providers; - } - - /** - * Create or Update an SMS provider - * @param array $params - * @return array saved values - */ - public static function create(&$params) { - $id = $params['id'] ?? NULL; - - if ($id) { - CRM_Utils_Hook::pre('edit', 'SmsProvider', $id, $params); - } - else { - CRM_Utils_Hook::pre('create', 'SmsProvider', NULL, $params); - } - - $provider = new CRM_SMS_DAO_Provider(); - if ($id) { - $provider->id = $id; - $provider->find(TRUE); - } - if ($id) { - $provider->domain_id = CRM_Utils_Array::value('domain_id', $params, $provider->domain_id); - } - else { - $provider->domain_id = CRM_Utils_Array::value('domain_id', $params, CRM_Core_Config::domainID()); - } - $provider->copyValues($params); - $result = $provider->save(); - if ($id) { - CRM_Utils_Hook::post('edit', 'SmsProvider', $provider->id, $provider); - } - else { - CRM_Utils_Hook::post('create', 'SmsProvider', NULL, $provider); - } - return $result; - } - - /** - * @deprecated - this bypasses hooks. - * @param int $id - * @param bool $is_active - * @return bool - */ - public static function setIsActive($id, $is_active) { - CRM_Core_Error::deprecatedFunctionWarning('writeRecord'); - return CRM_Core_DAO::setFieldValue('CRM_SMS_DAO_Provider', $id, 'is_active', $is_active); - } - - /** - * @param int $providerID - * - * @return null - * @throws CRM_Core_Exception - * - * @deprecated - */ - public static function del($providerID) { - if (!$providerID) { - throw new CRM_Core_Exception(ts('Invalid value passed to delete function.')); - } - - $dao = new CRM_SMS_DAO_Provider(); - $dao->id = $providerID; - $dao->whereAdd = "(domain_id = " . CRM_Core_Config::domainID() . "OR domain_id IS NULL)"; - if (!$dao->find(TRUE)) { - return NULL; - } - // The above just filters out attempts to delete for other domains - // Not sure it's needed, but preserves old behaviour and is deprecated. - static::deleteRecord(['id' => $providerID]); - } - - /** - * @param int $providerID - * @param string|null $returnParam - * @param string|null $returnDefaultString - * - * @return mixed - */ - public static function getProviderInfo($providerID, $returnParam = NULL, $returnDefaultString = NULL) { - static $providerInfo = []; - - if (!array_key_exists($providerID, $providerInfo)) { - $providerInfo[$providerID] = []; - - $dao = new CRM_SMS_DAO_Provider(); - $dao->id = $providerID; - if ($dao->find(TRUE)) { - CRM_Core_DAO::storeValues($dao, $providerInfo[$providerID]); - $inputLines = explode("\n", $providerInfo[$providerID]['api_params']); - $inputVals = []; - foreach ($inputLines as $value) { - if ($value) { - list($key, $val) = explode("=", $value); - $inputVals[trim($key)] = trim($val); - } - } - $providerInfo[$providerID]['api_params'] = $inputVals; - - // Replace the api_type ID with the string value - $apiTypes = CRM_Core_OptionGroup::values('sms_api_type'); - $apiTypeId = $providerInfo[$providerID]['api_type']; - $providerInfo[$providerID]['api_type'] = CRM_Utils_Array::value($apiTypeId, $apiTypes, $apiTypeId); - } - } - - if ($returnParam) { - return CRM_Utils_Array::value($returnParam, $providerInfo[$providerID], $returnDefaultString); - } - return $providerInfo[$providerID]; - } - -} +class_alias('CRM_SMS_BAO_SmsProvider', 'CRM_SMS_BAO_Provider'); diff --git a/www/modules/civicrm/CRM/SMS/BAO/SmsProvider.php b/www/modules/civicrm/CRM/SMS/BAO/SmsProvider.php new file mode 100644 index 000000000..a17a745f6 --- /dev/null +++ b/www/modules/civicrm/CRM/SMS/BAO/SmsProvider.php @@ -0,0 +1,179 @@ + [CRM_Core_Config::domainID(), 'Positive']]); + } + + /** + * Retrieves the list of providers from the database. + * + * $selectArr array of coloumns to fetch + * $getActive boolean to get active providers + * + * @param null $selectArr + * @param null $filter + * @param bool $getActive + * @param string $orderBy + * + * @return array + */ + public static function getProviders($selectArr = NULL, $filter = NULL, $getActive = TRUE, $orderBy = 'id') { + + $providers = []; + $temp = []; + $dao = new CRM_SMS_DAO_SmsProvider(); + if ($filter && !array_key_exists('is_active', $filter) && $getActive) { + $dao->is_active = 1; + } + if ($filter && is_array($filter)) { + foreach ($filter as $key => $value) { + $dao->$key = $value; + } + } + if ($selectArr && is_array($selectArr)) { + $select = implode(',', $selectArr); + $dao->selectAdd($select); + } + $dao->whereAdd("(domain_id = " . CRM_Core_Config::domainID() . " OR domain_id IS NULL)"); + $dao->orderBy($orderBy); + $dao->find(); + while ($dao->fetch()) { + CRM_Core_DAO::storeValues($dao, $temp); + $providers[$dao->id] = $temp; + } + return $providers; + } + + /** + * Create or Update an SMS provider + * @param array $params + * @return array saved values + */ + public static function create(&$params) { + $id = $params['id'] ?? NULL; + + if ($id) { + CRM_Utils_Hook::pre('edit', 'SmsProvider', $id, $params); + } + else { + CRM_Utils_Hook::pre('create', 'SmsProvider', NULL, $params); + } + + $provider = new CRM_SMS_DAO_SmsProvider(); + if ($id) { + $provider->id = $id; + $provider->find(TRUE); + } + if ($id) { + $provider->domain_id = CRM_Utils_Array::value('domain_id', $params, $provider->domain_id); + } + else { + $provider->domain_id = CRM_Utils_Array::value('domain_id', $params, CRM_Core_Config::domainID()); + } + $provider->copyValues($params); + $result = $provider->save(); + if ($id) { + CRM_Utils_Hook::post('edit', 'SmsProvider', $provider->id, $provider); + } + else { + CRM_Utils_Hook::post('create', 'SmsProvider', NULL, $provider); + } + return $result; + } + + /** + * @deprecated - this bypasses hooks. + * @param int $id + * @param bool $is_active + * @return bool + */ + public static function setIsActive($id, $is_active) { + CRM_Core_Error::deprecatedFunctionWarning('writeRecord'); + return CRM_Core_DAO::setFieldValue('CRM_SMS_DAO_SmsProvider', $id, 'is_active', $is_active); + } + + /** + * @param int $providerID + * + * @return null + * @throws CRM_Core_Exception + * + * @deprecated + */ + public static function del($providerID) { + if (!$providerID) { + throw new CRM_Core_Exception(ts('Invalid value passed to delete function.')); + } + + $dao = new CRM_SMS_DAO_SmsProvider(); + $dao->id = $providerID; + $dao->whereAdd = "(domain_id = " . CRM_Core_Config::domainID() . "OR domain_id IS NULL)"; + if (!$dao->find(TRUE)) { + return NULL; + } + // The above just filters out attempts to delete for other domains + // Not sure it's needed, but preserves old behaviour and is deprecated. + static::deleteRecord(['id' => $providerID]); + } + + /** + * @param int $providerID + * @param string|null $returnParam + * @param string|null $returnDefaultString + * + * @return mixed + */ + public static function getProviderInfo($providerID, $returnParam = NULL, $returnDefaultString = NULL) { + static $providerInfo = []; + + if (!array_key_exists($providerID, $providerInfo)) { + $providerInfo[$providerID] = []; + + $dao = new CRM_SMS_DAO_SmsProvider(); + $dao->id = $providerID; + if ($dao->find(TRUE)) { + CRM_Core_DAO::storeValues($dao, $providerInfo[$providerID]); + $inputLines = explode("\n", $providerInfo[$providerID]['api_params']); + $inputVals = []; + foreach ($inputLines as $value) { + if ($value) { + list($key, $val) = explode("=", $value); + $inputVals[trim($key)] = trim($val); + } + } + $providerInfo[$providerID]['api_params'] = $inputVals; + + // Replace the api_type ID with the string value + $apiTypes = CRM_Core_OptionGroup::values('sms_api_type'); + $apiTypeId = $providerInfo[$providerID]['api_type']; + $providerInfo[$providerID]['api_type'] = CRM_Utils_Array::value($apiTypeId, $apiTypes, $apiTypeId); + } + } + + if ($returnParam) { + return CRM_Utils_Array::value($returnParam, $providerInfo[$providerID], $returnDefaultString); + } + return $providerInfo[$providerID]; + } + +} diff --git a/www/modules/civicrm/CRM/SMS/DAO/Provider.php b/www/modules/civicrm/CRM/SMS/DAO/Provider.php index 3b6c01db9..4622b6683 100644 --- a/www/modules/civicrm/CRM/SMS/DAO/Provider.php +++ b/www/modules/civicrm/CRM/SMS/DAO/Provider.php @@ -1,462 +1,2 @@ 'civicrm/admin/sms/provider/edit?reset=1&action=add', - 'delete' => 'civicrm/admin/sms/provider/edit?reset=1&action=delete&id=[id]', - 'update' => 'civicrm/admin/sms/provider/edit?reset=1&action=update&id=[id]', - 'browse' => 'civicrm/admin/sms/provider?reset=1', - ]; - - /** - * SMS Provider ID - * - * @var int|string|null - * (SQL type: int unsigned) - * Note that values will be retrieved from the database as a string. - */ - public $id; - - /** - * Provider internal name points to option_value of option_group sms_provider_name - * - * @var string|null - * (SQL type: varchar(64)) - * Note that values will be retrieved from the database as a string. - */ - public $name; - - /** - * Provider name visible to user - * - * @var string|null - * (SQL type: varchar(64)) - * Note that values will be retrieved from the database as a string. - */ - public $title; - - /** - * @var string|null - * (SQL type: varchar(255)) - * Note that values will be retrieved from the database as a string. - */ - public $username; - - /** - * @var string|null - * (SQL type: varchar(255)) - * Note that values will be retrieved from the database as a string. - */ - public $password; - - /** - * points to value in civicrm_option_value for group sms_api_type - * - * @var int|string - * (SQL type: int unsigned) - * Note that values will be retrieved from the database as a string. - */ - public $api_type; - - /** - * @var string|null - * (SQL type: varchar(128)) - * Note that values will be retrieved from the database as a string. - */ - public $api_url; - - /** - * the api params in xml, http or smtp format - * - * @var string|null - * (SQL type: text) - * Note that values will be retrieved from the database as a string. - */ - public $api_params; - - /** - * @var bool|string - * (SQL type: tinyint) - * Note that values will be retrieved from the database as a string. - */ - public $is_default; - - /** - * @var bool|string - * (SQL type: tinyint) - * Note that values will be retrieved from the database as a string. - */ - public $is_active; - - /** - * Which Domain is this sms provider for - * - * @var int|string|null - * (SQL type: int unsigned) - * Note that values will be retrieved from the database as a string. - */ - public $domain_id; - - /** - * Class constructor. - */ - public function __construct() { - $this->__table = 'civicrm_sms_provider'; - parent::__construct(); - } - - /** - * Returns localized title of this entity. - * - * @param bool $plural - * Whether to return the plural version of the title. - */ - public static function getEntityTitle($plural = FALSE) { - return $plural ? ts('SMS Providers') : ts('SMS Provider'); - } - - /** - * Returns all the column names of this table - * - * @return array - */ - public static function &fields() { - if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = [ - 'id' => [ - 'name' => 'id', - 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('SMS Provider ID'), - 'description' => ts('SMS Provider ID'), - 'required' => TRUE, - 'usage' => [ - 'import' => FALSE, - 'export' => FALSE, - 'duplicate_matching' => FALSE, - 'token' => FALSE, - ], - 'where' => 'civicrm_sms_provider.id', - 'table_name' => 'civicrm_sms_provider', - 'entity' => 'Provider', - 'bao' => 'CRM_SMS_BAO_Provider', - 'localizable' => 0, - 'html' => [ - 'type' => 'Number', - ], - 'readonly' => TRUE, - 'add' => '4.2', - ], - 'name' => [ - 'name' => 'name', - 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('SMS Provider Name'), - 'description' => ts('Provider internal name points to option_value of option_group sms_provider_name'), - 'maxlength' => 64, - 'size' => CRM_Utils_Type::BIG, - 'usage' => [ - 'import' => FALSE, - 'export' => FALSE, - 'duplicate_matching' => FALSE, - 'token' => FALSE, - ], - 'where' => 'civicrm_sms_provider.name', - 'table_name' => 'civicrm_sms_provider', - 'entity' => 'Provider', - 'bao' => 'CRM_SMS_BAO_Provider', - 'localizable' => 0, - 'add' => '4.2', - ], - 'title' => [ - 'name' => 'title', - 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('SMS Provider Title'), - 'description' => ts('Provider name visible to user'), - 'maxlength' => 64, - 'size' => CRM_Utils_Type::BIG, - 'usage' => [ - 'import' => FALSE, - 'export' => FALSE, - 'duplicate_matching' => FALSE, - 'token' => FALSE, - ], - 'where' => 'civicrm_sms_provider.title', - 'table_name' => 'civicrm_sms_provider', - 'entity' => 'Provider', - 'bao' => 'CRM_SMS_BAO_Provider', - 'localizable' => 0, - 'html' => [ - 'type' => 'Text', - ], - 'add' => '4.2', - ], - 'username' => [ - 'name' => 'username', - 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('SMS Provider Username'), - 'maxlength' => 255, - 'size' => CRM_Utils_Type::HUGE, - 'usage' => [ - 'import' => FALSE, - 'export' => FALSE, - 'duplicate_matching' => FALSE, - 'token' => FALSE, - ], - 'where' => 'civicrm_sms_provider.username', - 'table_name' => 'civicrm_sms_provider', - 'entity' => 'Provider', - 'bao' => 'CRM_SMS_BAO_Provider', - 'localizable' => 0, - 'html' => [ - 'type' => 'Text', - ], - 'add' => '4.2', - ], - 'password' => [ - 'name' => 'password', - 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('SMS Provider Password'), - 'maxlength' => 255, - 'size' => CRM_Utils_Type::HUGE, - 'usage' => [ - 'import' => FALSE, - 'export' => FALSE, - 'duplicate_matching' => FALSE, - 'token' => FALSE, - ], - 'where' => 'civicrm_sms_provider.password', - 'table_name' => 'civicrm_sms_provider', - 'entity' => 'Provider', - 'bao' => 'CRM_SMS_BAO_Provider', - 'localizable' => 0, - 'html' => [ - 'type' => 'Text', - ], - 'add' => '4.2', - ], - 'api_type' => [ - 'name' => 'api_type', - 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('SMS Provider API'), - 'description' => ts('points to value in civicrm_option_value for group sms_api_type'), - 'required' => TRUE, - 'usage' => [ - 'import' => FALSE, - 'export' => FALSE, - 'duplicate_matching' => FALSE, - 'token' => FALSE, - ], - 'where' => 'civicrm_sms_provider.api_type', - 'table_name' => 'civicrm_sms_provider', - 'entity' => 'Provider', - 'bao' => 'CRM_SMS_BAO_Provider', - 'localizable' => 0, - 'html' => [ - 'type' => 'Select', - ], - 'pseudoconstant' => [ - 'optionGroupName' => 'sms_api_type', - 'optionEditPath' => 'civicrm/admin/options/sms_api_type', - ], - 'add' => '4.2', - ], - 'api_url' => [ - 'name' => 'api_url', - 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('SMS Provider API URL'), - 'maxlength' => 128, - 'size' => CRM_Utils_Type::HUGE, - 'usage' => [ - 'import' => FALSE, - 'export' => FALSE, - 'duplicate_matching' => FALSE, - 'token' => FALSE, - ], - 'where' => 'civicrm_sms_provider.api_url', - 'table_name' => 'civicrm_sms_provider', - 'entity' => 'Provider', - 'bao' => 'CRM_SMS_BAO_Provider', - 'localizable' => 0, - 'html' => [ - 'type' => 'Text', - ], - 'add' => '4.2', - ], - 'api_params' => [ - 'name' => 'api_params', - 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('SMS Provider API Params'), - 'description' => ts('the api params in xml, http or smtp format'), - 'usage' => [ - 'import' => FALSE, - 'export' => FALSE, - 'duplicate_matching' => FALSE, - 'token' => FALSE, - ], - 'where' => 'civicrm_sms_provider.api_params', - 'table_name' => 'civicrm_sms_provider', - 'entity' => 'Provider', - 'bao' => 'CRM_SMS_BAO_Provider', - 'localizable' => 0, - 'html' => [ - 'type' => 'Text', - ], - 'add' => '4.2', - ], - 'is_default' => [ - 'name' => 'is_default', - 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('SMS Provider is Default?'), - 'required' => TRUE, - 'usage' => [ - 'import' => FALSE, - 'export' => FALSE, - 'duplicate_matching' => FALSE, - 'token' => FALSE, - ], - 'where' => 'civicrm_sms_provider.is_default', - 'default' => '0', - 'table_name' => 'civicrm_sms_provider', - 'entity' => 'Provider', - 'bao' => 'CRM_SMS_BAO_Provider', - 'localizable' => 0, - 'html' => [ - 'type' => 'CheckBox', - 'label' => ts("Default"), - ], - 'add' => '4.2', - ], - 'is_active' => [ - 'name' => 'is_active', - 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('SMS Provider is Active?'), - 'required' => TRUE, - 'usage' => [ - 'import' => FALSE, - 'export' => FALSE, - 'duplicate_matching' => FALSE, - 'token' => FALSE, - ], - 'where' => 'civicrm_sms_provider.is_active', - 'default' => '1', - 'table_name' => 'civicrm_sms_provider', - 'entity' => 'Provider', - 'bao' => 'CRM_SMS_BAO_Provider', - 'localizable' => 0, - 'html' => [ - 'type' => 'CheckBox', - 'label' => ts("Enabled"), - ], - 'add' => '4.2', - ], - 'domain_id' => [ - 'name' => 'domain_id', - 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Domain ID'), - 'description' => ts('Which Domain is this sms provider for'), - 'usage' => [ - 'import' => FALSE, - 'export' => FALSE, - 'duplicate_matching' => FALSE, - 'token' => FALSE, - ], - 'where' => 'civicrm_sms_provider.domain_id', - 'table_name' => 'civicrm_sms_provider', - 'entity' => 'Provider', - 'bao' => 'CRM_SMS_BAO_Provider', - 'localizable' => 0, - 'FKClassName' => 'CRM_Core_DAO_Domain', - 'FKColumnName' => 'id', - 'html' => [ - 'label' => ts("Domain"), - ], - 'pseudoconstant' => [ - 'table' => 'civicrm_domain', - 'keyColumn' => 'id', - 'labelColumn' => 'name', - ], - 'add' => '4.7', - ], - ]; - CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); - } - return Civi::$statics[__CLASS__]['fields']; - } - - /** - * Returns the list of fields that can be imported - * - * @param bool $prefix - * - * @return array - */ - public static function &import($prefix = FALSE) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'sms_provider', $prefix, []); - return $r; - } - - /** - * Returns the list of fields that can be exported - * - * @param bool $prefix - * - * @return array - */ - public static function &export($prefix = FALSE) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'sms_provider', $prefix, []); - return $r; - } - - /** - * Returns the list of indices - * - * @param bool $localize - * - * @return array - */ - public static function indices($localize = TRUE) { - $indices = []; - return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; - } - -} +class_alias('CRM_SMS_DAO_SmsProvider', 'CRM_SMS_DAO_Provider'); diff --git a/www/modules/civicrm/CRM/SMS/DAO/SmsProvider.php b/www/modules/civicrm/CRM/SMS/DAO/SmsProvider.php new file mode 100644 index 000000000..da3a9d2b7 --- /dev/null +++ b/www/modules/civicrm/CRM/SMS/DAO/SmsProvider.php @@ -0,0 +1,462 @@ + 'civicrm/admin/sms/provider/edit?reset=1&action=add', + 'delete' => 'civicrm/admin/sms/provider/edit?reset=1&action=delete&id=[id]', + 'update' => 'civicrm/admin/sms/provider/edit?reset=1&action=update&id=[id]', + 'browse' => 'civicrm/admin/sms/provider?reset=1', + ]; + + /** + * SMS Provider ID + * + * @var int|string|null + * (SQL type: int unsigned) + * Note that values will be retrieved from the database as a string. + */ + public $id; + + /** + * Provider internal name points to option_value of option_group sms_provider_name + * + * @var string|null + * (SQL type: varchar(64)) + * Note that values will be retrieved from the database as a string. + */ + public $name; + + /** + * Provider name visible to user + * + * @var string|null + * (SQL type: varchar(64)) + * Note that values will be retrieved from the database as a string. + */ + public $title; + + /** + * @var string|null + * (SQL type: varchar(255)) + * Note that values will be retrieved from the database as a string. + */ + public $username; + + /** + * @var string|null + * (SQL type: varchar(255)) + * Note that values will be retrieved from the database as a string. + */ + public $password; + + /** + * points to value in civicrm_option_value for group sms_api_type + * + * @var int|string + * (SQL type: int unsigned) + * Note that values will be retrieved from the database as a string. + */ + public $api_type; + + /** + * @var string|null + * (SQL type: varchar(128)) + * Note that values will be retrieved from the database as a string. + */ + public $api_url; + + /** + * the api params in xml, http or smtp format + * + * @var string|null + * (SQL type: text) + * Note that values will be retrieved from the database as a string. + */ + public $api_params; + + /** + * @var bool|string + * (SQL type: tinyint) + * Note that values will be retrieved from the database as a string. + */ + public $is_default; + + /** + * @var bool|string + * (SQL type: tinyint) + * Note that values will be retrieved from the database as a string. + */ + public $is_active; + + /** + * Which Domain is this sms provider for + * + * @var int|string|null + * (SQL type: int unsigned) + * Note that values will be retrieved from the database as a string. + */ + public $domain_id; + + /** + * Class constructor. + */ + public function __construct() { + $this->__table = 'civicrm_sms_provider'; + parent::__construct(); + } + + /** + * Returns localized title of this entity. + * + * @param bool $plural + * Whether to return the plural version of the title. + */ + public static function getEntityTitle($plural = FALSE) { + return $plural ? ts('SMS Providers') : ts('SMS Provider'); + } + + /** + * Returns all the column names of this table + * + * @return array + */ + public static function &fields() { + if (!isset(Civi::$statics[__CLASS__]['fields'])) { + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ + 'name' => 'id', + 'type' => CRM_Utils_Type::T_INT, + 'title' => ts('SMS Provider ID'), + 'description' => ts('SMS Provider ID'), + 'required' => TRUE, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_sms_provider.id', + 'table_name' => 'civicrm_sms_provider', + 'entity' => 'SmsProvider', + 'bao' => 'CRM_SMS_BAO_SmsProvider', + 'localizable' => 0, + 'html' => [ + 'type' => 'Number', + ], + 'readonly' => TRUE, + 'add' => '4.2', + ], + 'name' => [ + 'name' => 'name', + 'type' => CRM_Utils_Type::T_STRING, + 'title' => ts('SMS Provider Name'), + 'description' => ts('Provider internal name points to option_value of option_group sms_provider_name'), + 'maxlength' => 64, + 'size' => CRM_Utils_Type::BIG, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_sms_provider.name', + 'table_name' => 'civicrm_sms_provider', + 'entity' => 'SmsProvider', + 'bao' => 'CRM_SMS_BAO_SmsProvider', + 'localizable' => 0, + 'add' => '4.2', + ], + 'title' => [ + 'name' => 'title', + 'type' => CRM_Utils_Type::T_STRING, + 'title' => ts('SMS Provider Title'), + 'description' => ts('Provider name visible to user'), + 'maxlength' => 64, + 'size' => CRM_Utils_Type::BIG, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_sms_provider.title', + 'table_name' => 'civicrm_sms_provider', + 'entity' => 'SmsProvider', + 'bao' => 'CRM_SMS_BAO_SmsProvider', + 'localizable' => 0, + 'html' => [ + 'type' => 'Text', + ], + 'add' => '4.2', + ], + 'username' => [ + 'name' => 'username', + 'type' => CRM_Utils_Type::T_STRING, + 'title' => ts('SMS Provider Username'), + 'maxlength' => 255, + 'size' => CRM_Utils_Type::HUGE, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_sms_provider.username', + 'table_name' => 'civicrm_sms_provider', + 'entity' => 'SmsProvider', + 'bao' => 'CRM_SMS_BAO_SmsProvider', + 'localizable' => 0, + 'html' => [ + 'type' => 'Text', + ], + 'add' => '4.2', + ], + 'password' => [ + 'name' => 'password', + 'type' => CRM_Utils_Type::T_STRING, + 'title' => ts('SMS Provider Password'), + 'maxlength' => 255, + 'size' => CRM_Utils_Type::HUGE, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_sms_provider.password', + 'table_name' => 'civicrm_sms_provider', + 'entity' => 'SmsProvider', + 'bao' => 'CRM_SMS_BAO_SmsProvider', + 'localizable' => 0, + 'html' => [ + 'type' => 'Text', + ], + 'add' => '4.2', + ], + 'api_type' => [ + 'name' => 'api_type', + 'type' => CRM_Utils_Type::T_INT, + 'title' => ts('SMS Provider API'), + 'description' => ts('points to value in civicrm_option_value for group sms_api_type'), + 'required' => TRUE, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_sms_provider.api_type', + 'table_name' => 'civicrm_sms_provider', + 'entity' => 'SmsProvider', + 'bao' => 'CRM_SMS_BAO_SmsProvider', + 'localizable' => 0, + 'html' => [ + 'type' => 'Select', + ], + 'pseudoconstant' => [ + 'optionGroupName' => 'sms_api_type', + 'optionEditPath' => 'civicrm/admin/options/sms_api_type', + ], + 'add' => '4.2', + ], + 'api_url' => [ + 'name' => 'api_url', + 'type' => CRM_Utils_Type::T_STRING, + 'title' => ts('SMS Provider API URL'), + 'maxlength' => 128, + 'size' => CRM_Utils_Type::HUGE, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_sms_provider.api_url', + 'table_name' => 'civicrm_sms_provider', + 'entity' => 'SmsProvider', + 'bao' => 'CRM_SMS_BAO_SmsProvider', + 'localizable' => 0, + 'html' => [ + 'type' => 'Text', + ], + 'add' => '4.2', + ], + 'api_params' => [ + 'name' => 'api_params', + 'type' => CRM_Utils_Type::T_TEXT, + 'title' => ts('SMS Provider API Params'), + 'description' => ts('the api params in xml, http or smtp format'), + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_sms_provider.api_params', + 'table_name' => 'civicrm_sms_provider', + 'entity' => 'SmsProvider', + 'bao' => 'CRM_SMS_BAO_SmsProvider', + 'localizable' => 0, + 'html' => [ + 'type' => 'Text', + ], + 'add' => '4.2', + ], + 'is_default' => [ + 'name' => 'is_default', + 'type' => CRM_Utils_Type::T_BOOLEAN, + 'title' => ts('SMS Provider is Default?'), + 'required' => TRUE, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_sms_provider.is_default', + 'default' => '0', + 'table_name' => 'civicrm_sms_provider', + 'entity' => 'SmsProvider', + 'bao' => 'CRM_SMS_BAO_SmsProvider', + 'localizable' => 0, + 'html' => [ + 'type' => 'CheckBox', + 'label' => ts("Default"), + ], + 'add' => '4.2', + ], + 'is_active' => [ + 'name' => 'is_active', + 'type' => CRM_Utils_Type::T_BOOLEAN, + 'title' => ts('SMS Provider is Active?'), + 'required' => TRUE, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_sms_provider.is_active', + 'default' => '1', + 'table_name' => 'civicrm_sms_provider', + 'entity' => 'SmsProvider', + 'bao' => 'CRM_SMS_BAO_SmsProvider', + 'localizable' => 0, + 'html' => [ + 'type' => 'CheckBox', + 'label' => ts("Enabled"), + ], + 'add' => '4.2', + ], + 'domain_id' => [ + 'name' => 'domain_id', + 'type' => CRM_Utils_Type::T_INT, + 'title' => ts('Domain ID'), + 'description' => ts('Which Domain is this sms provider for'), + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_sms_provider.domain_id', + 'table_name' => 'civicrm_sms_provider', + 'entity' => 'SmsProvider', + 'bao' => 'CRM_SMS_BAO_SmsProvider', + 'localizable' => 0, + 'FKClassName' => 'CRM_Core_DAO_Domain', + 'FKColumnName' => 'id', + 'html' => [ + 'label' => ts("Domain"), + ], + 'pseudoconstant' => [ + 'table' => 'civicrm_domain', + 'keyColumn' => 'id', + 'labelColumn' => 'name', + ], + 'add' => '4.7', + ], + ]; + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); + } + return Civi::$statics[__CLASS__]['fields']; + } + + /** + * Returns the list of fields that can be imported + * + * @param bool $prefix + * + * @return array + */ + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'sms_provider', $prefix, []); + return $r; + } + + /** + * Returns the list of fields that can be exported + * + * @param bool $prefix + * + * @return array + */ + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'sms_provider', $prefix, []); + return $r; + } + + /** + * Returns the list of indices + * + * @param bool $localize + * + * @return array + */ + public static function indices($localize = TRUE) { + $indices = []; + return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; + } + +} diff --git a/www/modules/civicrm/CRM/SMS/Form/Group.php b/www/modules/civicrm/CRM/SMS/Form/Group.php index 897ea179c..afc86af18 100644 --- a/www/modules/civicrm/CRM/SMS/Form/Group.php +++ b/www/modules/civicrm/CRM/SMS/Form/Group.php @@ -24,7 +24,7 @@ class CRM_SMS_Form_Group extends CRM_Contact_Form_Task { * Set variables up before form is built. */ public function preProcess() { - if (!CRM_SMS_BAO_Provider::activeProviderCount()) { + if (!CRM_SMS_BAO_SmsProvider::activeProviderCount()) { CRM_Core_Error::statusBounce(ts('The SMS Provider has not been configured or is not active.', [1 => CRM_Utils_System::url('civicrm/admin/sms/provider', 'reset=1')])); } @@ -70,8 +70,8 @@ public function setDefaultValues() { $defaults['includeGroups'] = $mailingGroups['civicrm_group']['Include']; $defaults['excludeGroups'] = $mailingGroups['civicrm_group']['Exclude'] ?? NULL; - $defaults['includeMailings'] = CRM_Utils_Array::value('Include', CRM_Utils_Array::value('civicrm_mailing', $mailingGroups)); - $defaults['excludeMailings'] = CRM_Utils_Array::value('Exclude', CRM_Utils_Array::value('civicrm_mailing', $mailingGroups)); + $defaults['includeMailings'] = $mailingGroups['civicrm_mailing']['Include'] ?? NULL; + $defaults['excludeMailings'] = $mailingGroups['civicrm_mailing']['Exclude'] ?? NULL; } return $defaults; @@ -94,7 +94,7 @@ public function buildQuickForm() { $this->add('select', 'sms_provider_id', ts('Select SMS Provider'), - CRM_Utils_Array::collect('title', CRM_SMS_BAO_Provider::getProviders(NULL, ['is_active' => 1])), + CRM_Utils_Array::collect('title', CRM_SMS_BAO_SmsProvider::getProviders(NULL, ['is_active' => 1])), TRUE ); @@ -175,7 +175,7 @@ public function postProcess() { $params[$n] = $values[$n]; if ($n == 'sms_provider_id') { // Get the from Name. - $params['from_name'] = CRM_Core_DAO::getFieldValue('CRM_SMS_DAO_Provider', $params['sms_provider_id'], 'username'); + $params['from_name'] = CRM_Core_DAO::getFieldValue('CRM_SMS_DAO_SmsProvider', $params['sms_provider_id'], 'username'); } } } diff --git a/www/modules/civicrm/CRM/SMS/Form/Provider.php b/www/modules/civicrm/CRM/SMS/Form/Provider.php index ed52c8450..f39062ab8 100644 --- a/www/modules/civicrm/CRM/SMS/Form/Provider.php +++ b/www/modules/civicrm/CRM/SMS/Form/Provider.php @@ -33,13 +33,13 @@ public function preProcess() { $this->setPageTitle(ts('SMS Provider')); if ($this->_id) { - $refreshURL = CRM_Utils_System::url('civicrm/admin/sms/provider', + $refreshURL = CRM_Utils_System::url('civicrm/admin/sms/provider/edit', "reset=1&action=update&id={$this->_id}", FALSE, NULL, FALSE ); } else { - $refreshURL = CRM_Utils_System::url('civicrm/admin/sms/provider', + $refreshURL = CRM_Utils_System::url('civicrm/admin/sms/provider/edit', "reset=1&action=add", FALSE, NULL, FALSE ); @@ -70,7 +70,7 @@ public function buildQuickForm() { return; } - $attributes = CRM_Core_DAO::getAttribute('CRM_SMS_DAO_Provider'); + $attributes = CRM_Core_DAO::getAttribute('CRM_SMS_DAO_SmsProvider'); $providerNames = CRM_Core_OptionGroup::values('sms_provider_name', FALSE, FALSE, FALSE, NULL, 'label'); $apiTypes = CRM_Core_OptionGroup::values('sms_api_type', FALSE, FALSE, FALSE, NULL, 'label'); @@ -82,7 +82,7 @@ public function buildQuickForm() { ); $this->addRule('title', ts('This Title already exists in Database.'), 'objectExists', [ - 'CRM_SMS_DAO_Provider', + 'CRM_SMS_DAO_SmsProvider', $this->_id, ]); @@ -127,7 +127,7 @@ public function setDefaultValues() { return $defaults; } - $dao = new CRM_SMS_DAO_Provider(); + $dao = new CRM_SMS_DAO_SmsProvider(); $dao->id = $this->_id; if ($name) { @@ -151,7 +151,7 @@ public function postProcess() { CRM_Utils_System::flushCache(); if ($this->_action & CRM_Core_Action::DELETE) { - CRM_SMS_BAO_Provider::del($this->_id); + CRM_SMS_BAO_SmsProvider::del($this->_id); CRM_Core_Session::setStatus(ts('Selected Provider has been deleted.'), ts('Deleted'), 'success'); return; } diff --git a/www/modules/civicrm/CRM/SMS/Form/Schedule.php b/www/modules/civicrm/CRM/SMS/Form/Schedule.php index d702d4f7b..b698b79e1 100644 --- a/www/modules/civicrm/CRM/SMS/Form/Schedule.php +++ b/www/modules/civicrm/CRM/SMS/Form/Schedule.php @@ -86,7 +86,7 @@ public function buildQuickform() { $preview = []; $preview['type'] = CRM_Core_DAO::getFieldValue('CRM_Mailing_DAO_Mailing', $this->_mailingID, 'body_html') ? 'html' : 'text'; $preview['viewURL'] = CRM_Utils_System::url('civicrm/mailing/view', "reset=1&id={$this->_mailingID}"); - $this->assign_by_ref('preview', $preview); + $this->assign('preview', $preview); } /** diff --git a/www/modules/civicrm/CRM/SMS/Page/Provider.php b/www/modules/civicrm/CRM/SMS/Page/Provider.php index 0024d45e2..0838f21d0 100644 --- a/www/modules/civicrm/CRM/SMS/Page/Provider.php +++ b/www/modules/civicrm/CRM/SMS/Page/Provider.php @@ -29,7 +29,7 @@ class CRM_SMS_Page_Provider extends CRM_Core_Page_Basic { * Classname of BAO. */ public function getBAOName() { - return 'CRM_SMS_BAO_Provider'; + return 'CRM_SMS_BAO_SmsProvider'; } /** @@ -61,7 +61,7 @@ public function run() { * @param array $action */ public function browse($action = NULL) { - $providers = CRM_SMS_BAO_Provider::getProviders(); + $providers = CRM_SMS_BAO_SmsProvider::getProviders(); $rows = []; foreach ($providers as $provider) { $action = array_sum(array_keys($this->links())); diff --git a/www/modules/civicrm/CRM/SMS/Provider.php b/www/modules/civicrm/CRM/SMS/Provider.php index b711f2afb..8828f5a6a 100644 --- a/www/modules/civicrm/CRM/SMS/Provider.php +++ b/www/modules/civicrm/CRM/SMS/Provider.php @@ -44,7 +44,7 @@ public static function &singleton($providerParams = [], $force = FALSE) { $providerParams['provider_id'] = $providerID; } if ($providerID) { - $providerName = CRM_SMS_BAO_Provider::getProviderInfo($providerID, 'name'); + $providerName = CRM_SMS_BAO_SmsProvider::getProviderInfo($providerID, 'name'); } if (!$providerName) { @@ -169,8 +169,8 @@ public function processInbound($from, $body, $to = NULL, $trackID = NULL) { if (!$message->fromContactID) { // find sender by phone number if $fromContactID not set by hook - $formatFrom = '%' . $this->formatPhone($this->stripPhone($message->from), $like, "like"); - $message->fromContactID = CRM_Core_DAO::singleValueQuery("SELECT contact_id FROM civicrm_phone JOIN civicrm_contact ON civicrm_contact.id = civicrm_phone.contact_id WHERE !civicrm_contact.is_deleted AND phone LIKE %1", [ + $formatFrom = '%' . $this->formatPhoneNumber($this->stripPhone($message->from)); + $message->fromContactID = CRM_Core_DAO::singleValueQuery("SELECT contact_id FROM civicrm_phone JOIN civicrm_contact ON civicrm_contact.id = civicrm_phone.contact_id WHERE !civicrm_contact.is_deleted AND phone_numeric LIKE %1", [ 1 => [$formatFrom, 'String'], ]); } @@ -242,7 +242,7 @@ public function processInbound($from, $body, $to = NULL, $trackID = NULL) { * * @return mixed|string */ - public function stripPhone($phone) { + public function stripPhone($phone): string { $newphone = preg_replace('/[^0-9x]/', '', $phone); while (substr($newphone, 0, 1) == "1") { $newphone = substr($newphone, 1); @@ -253,7 +253,49 @@ public function stripPhone($phone) { while (substr($newphone, -1) == "x") { $newphone = substr($newphone, 0, -1); } - return $newphone; + return (string) $newphone; + } + + /** + * Format phone number with % - this may no longer make sense as we + * now compare with phone_numeric. + * + * @param string $phone + * + * @return string + */ + private function formatPhoneNumber(string $phone): string { + $phoneA = explode("x", $phone); + switch (strlen($phoneA[0])) { + case 0: + $area = ""; + $exch = ""; + $uniq = ""; + $ext = $phoneA[1]; + break; + + case 7: + $area = ""; + $exch = substr($phone, 0, 3); + $uniq = substr($phone, 3, 4); + $ext = $phoneA[1]; + break; + + case 10: + $area = substr($phone, 0, 3); + $exch = substr($phone, 3, 3); + $uniq = substr($phone, 6, 4); + $ext = $phoneA[1]; + break; + + default: + return $phone; + } + + $newphone = '%' . $area . '%' . $exch . '%' . $uniq . '%' . $ext . '%'; + $newphone = str_replace('%%', '%', $newphone); + $newphone = str_replace('%%', '%', $newphone); + return (string) $newphone; } /** @@ -261,9 +303,12 @@ public function stripPhone($phone) { * @param $kind * @param string $format * + * @deprecated since 5.73 will be removed around 5.95 + * * @return mixed|string */ public function formatPhone($phone, &$kind, $format = "dash") { + CRM_Core_Error::deprecatedFunctionWarning('unused'); $phoneA = explode("x", $phone); switch (strlen($phoneA[0])) { case 0: diff --git a/www/modules/civicrm/CRM/UF/Form/Field.php b/www/modules/civicrm/CRM/UF/Form/Field.php index 3c278e186..0d7b7cb18 100644 --- a/www/modules/civicrm/CRM/UF/Form/Field.php +++ b/www/modules/civicrm/CRM/UF/Form/Field.php @@ -240,8 +240,7 @@ public function buildQuickForm() { foreach ($value as $key1 => $value1) { //CRM-2676, replacing the conflict for same custom field name from different custom group. if ($customFieldId = CRM_Core_BAO_CustomField::getKeyID($key1)) { - $customGroupId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', $customFieldId, 'custom_group_id'); - $customGroupName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $customGroupId, 'title'); + $customGroupName = CRM_Core_BAO_CustomField::getField($customFieldId)['custom_group']['title']; $this->_mapperFields[$key][$key1] = $value1['title'] . ' :: ' . $customGroupName; if (in_array($key1, $addressCustomFields)) { $noSearchable[] = $value1['title'] . ' :: ' . $customGroupName; @@ -750,9 +749,7 @@ public static function formRule($fields, $files, $self) { //get custom field id $customFieldId = explode('_', $profileFieldName); if ($customFieldId[0] == 'custom') { - $customField = new CRM_Core_DAO_CustomField(); - $customField->id = $customFieldId[1]; - $customField->find(TRUE); + $customField = CRM_Core_BAO_CustomField::getFieldObject($customFieldId[1]); $isCustomField = TRUE; if (!empty($fields['field_id']) && !$customField->is_active && $is_active) { $errors['field_name'] = ts('Cannot set this field "Active" since the selected custom field is disabled.'); diff --git a/www/modules/civicrm/CRM/UF/Form/Group.php b/www/modules/civicrm/CRM/UF/Form/Group.php index 37d340ebf..078a709c1 100644 --- a/www/modules/civicrm/CRM/UF/Form/Group.php +++ b/www/modules/civicrm/CRM/UF/Form/Group.php @@ -30,10 +30,10 @@ class CRM_UF_Form_Group extends CRM_Core_Form { /** * Set entity fields to be assigned to the form. */ - protected function setEntityFields() { + protected function setEntityFields(): void { $this->entityFields = [ - 'title' => ['name' => 'title'], - 'frontend_title' => ['name' => 'frontend_title'], + 'title' => ['name' => 'title', 'required' => TRUE], + 'frontend_title' => ['name' => 'frontend_title', 'required' => TRUE], 'description' => [ 'name' => 'description', 'help' => ['id' => 'id-description', 'file' => 'CRM/UF/Form/Group.hlp'], diff --git a/www/modules/civicrm/CRM/UF/Page/ProfileEditor.php b/www/modules/civicrm/CRM/UF/Page/ProfileEditor.php index 8222086dd..e68edee16 100644 --- a/www/modules/civicrm/CRM/UF/Page/ProfileEditor.php +++ b/www/modules/civicrm/CRM/UF/Page/ProfileEditor.php @@ -300,33 +300,28 @@ public static function convertCiviModelToBackboneModel($extends, $title, $availa 'is_addable' => FALSE, ]; - $customGroup = CRM_Core_BAO_CustomGroup::getAllCustomGroupsByBaseEntity($extends); - $customGroup->orderBy('weight'); - $customGroup->is_active = 1; - $customGroup->find(); - while ($customGroup->fetch()) { - $sectionName = 'cg_' . $customGroup->id; + $customGroups = \CRM_Core_BAO_CustomGroup::getAll(['extends' => $extends, 'is_active' => TRUE]); + foreach ($customGroups as $customGroup) { + $sectionName = 'cg_' . $customGroup['id']; $section = [ - 'title' => ts('%1: %2', [1 => $title, 2 => $customGroup->title]), - 'is_addable' => !$customGroup->is_reserved, - 'custom_group_id' => $customGroup->id, - 'extends_entity_column_id' => $customGroup->extends_entity_column_id, - 'extends_entity_column_value' => CRM_Utils_Array::explodePadded($customGroup->extends_entity_column_value), - 'is_reserved' => (bool) $customGroup->is_reserved, + 'title' => ts('%1: %2', [1 => $title, 2 => $customGroup['title']]), + 'is_addable' => !$customGroup['is_reserved'], + 'custom_group_id' => (string) $customGroup['id'], + 'extends_entity_column_id' => $customGroup['extends_entity_column_id'], + 'extends_entity_column_value' => $customGroup['extends_entity_column_value'], + 'is_reserved' => $customGroup['is_reserved'], ]; $result['sections'][$sectionName] = $section; - } - - // put fields in their sections - $fields = CRM_Core_BAO_CustomField::getFields($extends); - foreach ($fields as $fieldId => $field) { - $sectionName = 'cg_' . $field['custom_group_id']; - $fieldName = 'custom_' . $fieldId; - if (isset($result['schema'][$fieldName])) { - $result['schema'][$fieldName]['section'] = $sectionName; - $result['schema'][$fieldName]['civiIsMultiple'] = (bool) CRM_Core_BAO_CustomField::isMultiRecordField($fieldId); + // put fields in their sections + foreach ($customGroup['fields'] as $field) { + $fieldName = 'custom_' . $field['id']; + if (isset($result['schema'][$fieldName])) { + $result['schema'][$fieldName]['section'] = $sectionName; + $result['schema'][$fieldName]['civiIsMultiple'] = $customGroup['is_multiple']; + } } } + return $result; } diff --git a/www/modules/civicrm/CRM/Upgrade/Incremental/General.php b/www/modules/civicrm/CRM/Upgrade/Incremental/General.php index d76f641cc..db5d392f5 100644 --- a/www/modules/civicrm/CRM/Upgrade/Incremental/General.php +++ b/www/modules/civicrm/CRM/Upgrade/Incremental/General.php @@ -15,6 +15,8 @@ * @copyright CiviCRM LLC https://civicrm.org/licensing */ +use Civi\Api4\Extension; + /** * This class contains generic upgrade logic which runs regardless of version. */ @@ -134,8 +136,7 @@ public static function setPreUpgradeMessage(&$preUpgradeMessage, $currentVer, $l } $ftAclSetting = Civi::settings()->get('acl_financial_type'); - $financialAclExtension = civicrm_api3('extension', 'get', ['key' => 'biz.jmaconsulting.financialaclreport', 'sequential' => 1]); - if ($ftAclSetting && (($financialAclExtension['count'] == 1 && $financialAclExtension['values'][0]['status'] != 'Installed') || $financialAclExtension['count'] !== 1)) { + if ($ftAclSetting && !self::isExtensionInstalled('biz.jmaconsulting.financialaclreport')) { $preUpgradeMessage .= '
' . ts('CiviCRM will in the future require the extension %1 for CiviCRM Reports to work correctly with the Financial Type ACLs. The extension can be downloaded here', [ 1 => 'biz.jmaconsulting.financialaclreport', 2 => 'https://github.com/JMAConsulting/biz.jmaconsulting.financialaclreport', @@ -185,4 +186,14 @@ public static function updateMessageTemplate(&$message, $version) { $messageObj->updateTemplates(); } + private static function isExtensionInstalled(string $key): bool { + $extension = Extension::get(FALSE) + ->addWhere('key', '=', $key) + ->addWhere('status', '=', 'Installed') + ->selectRowCount() + ->execute(); + + return $extension->countMatched() === 1; + } + } diff --git a/www/modules/civicrm/CRM/Upgrade/Incremental/MessageTemplates.php b/www/modules/civicrm/CRM/Upgrade/Incremental/MessageTemplates.php index 25a286982..42a1e6c52 100644 --- a/www/modules/civicrm/CRM/Upgrade/Incremental/MessageTemplates.php +++ b/www/modules/civicrm/CRM/Upgrade/Incremental/MessageTemplates.php @@ -377,6 +377,14 @@ protected function getTemplateUpdates() { ['name' => 'membership_online_receipt', 'type' => 'subject'], ], ], + [ + 'version' => '5.74.alpha1', + 'upgrade_descriptor' => ts('Minor space issue in string'), + 'templates' => [ + ['name' => 'event_online_receipt', 'type' => 'text'], + ['name' => 'event_online_receipt', 'type' => 'html'], + ], + ], ]; } diff --git a/www/modules/civicrm/CRM/Upgrade/Incremental/php/FiveFiftyOne.php b/www/modules/civicrm/CRM/Upgrade/Incremental/php/FiveFiftyOne.php index fd4520893..024928824 100644 --- a/www/modules/civicrm/CRM/Upgrade/Incremental/php/FiveFiftyOne.php +++ b/www/modules/civicrm/CRM/Upgrade/Incremental/php/FiveFiftyOne.php @@ -153,7 +153,7 @@ public static function convertMappingFieldLabelsToNames(): bool { ->addWhere('mapping_id.mapping_type_id:name', '=', 'Import Participant') ->execute(); - $fields = CRM_Event_BAO_Participant::importableFields('All', FALSE); + $fields = self::getImportableParticipantFields('All', FALSE); $fields['event_id']['title'] = 'Event ID'; $eventfields = CRM_Event_BAO_Event::fields(); $fields['event_title'] = $eventfields['event_title']; @@ -245,6 +245,96 @@ public static function convertMappingFieldLabelsToNames(): bool { return TRUE; } + /** + * Combine all the importable fields from the lower levels object. + * + * @return array + * array of importable Fields + */ + protected static function getImportableParticipantFields(): array { + $fields = ['' => ['title' => ts('- do not import -')]]; + $tmpFields = CRM_Event_DAO_Participant::import(); + + $note = [ + 'participant_note' => [ + 'title' => ts('Participant Note'), + 'name' => 'participant_note', + 'headerPattern' => '/(participant.)?note$/i', + 'data_type' => CRM_Utils_Type::T_TEXT, + ], + ]; + + // Split status and status id into 2 fields + // Fixme: it would be better to leave as 1 field and intelligently handle both during import + // note import undoes this - it is still here in case the search usage uses it. + $participantStatus = [ + 'participant_status' => [ + 'title' => ts('Participant Status'), + 'name' => 'participant_status', + 'data_type' => CRM_Utils_Type::T_STRING, + ], + ]; + $tmpFields['participant_status_id']['title'] = ts('Participant Status Id'); + + // Split role and role id into 2 fields + // Fixme: it would be better to leave as 1 field and intelligently handle both during import + // note import undoes this - it is still here in case the search usage uses it. + $participantRole = [ + 'participant_role' => [ + 'title' => ts('Participant Role'), + 'name' => 'participant_role', + 'data_type' => CRM_Utils_Type::T_STRING, + ], + ]; + $tmpFields['participant_role_id']['title'] = ts('Participant Role Id'); + + $eventType = [ + 'event_type' => [ + 'title' => ts('Event Type'), + 'name' => 'event_type', + 'data_type' => CRM_Utils_Type::T_STRING, + ], + ]; + + $tmpContactField = []; + $contactFields = CRM_Contact_BAO_Contact::importableFields('All', NULL); + + // Using new Dedupe rule. + $ruleParams = [ + 'contact_type' => 'All', + 'used' => 'Unsupervised', + ]; + $fieldsArray = CRM_Dedupe_BAO_DedupeRule::dedupeRuleFields($ruleParams); + + if (is_array($fieldsArray)) { + foreach ($fieldsArray as $value) { + $customFieldId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', + $value, + 'id', + 'column_name' + ); + $value = $customFieldId ? 'custom_' . $customFieldId : $value; + $tmpContactField[trim($value)] = $contactFields[trim($value)] ?? NULL; + $title = $tmpContactField[trim($value)]['title'] . ' (match to contact)'; + + $tmpContactField[trim($value)]['title'] = $title; + } + } + $extIdentifier = $contactFields['external_identifier'] ?? NULL; + if ($extIdentifier) { + $tmpContactField['external_identifier'] = $extIdentifier; + $tmpContactField['external_identifier']['title'] = ($extIdentifier['title'] ?? '') . ' (match to contact)'; + } + $tmpFields['participant_contact_id']['title'] = $tmpFields['participant_contact_id']['title'] . ' (match to contact)'; + + $fields = array_merge($fields, $tmpContactField); + $fields = array_merge($fields, $tmpFields); + $fields = array_merge($fields, $note, $participantStatus, $participantRole, $eventType); + $fields = array_merge($fields, CRM_Core_BAO_CustomField::getFieldsForImport('Participant', FALSE, FALSE, FALSE, FALSE)); + + return $fields; + } + /** * @param string $contactType * diff --git a/www/modules/civicrm/CRM/Upgrade/Incremental/php/FiveSeventyFour.php b/www/modules/civicrm/CRM/Upgrade/Incremental/php/FiveSeventyFour.php new file mode 100644 index 000000000..d9609938f --- /dev/null +++ b/www/modules/civicrm/CRM/Upgrade/Incremental/php/FiveSeventyFour.php @@ -0,0 +1,38 @@ +addTask(ts('Upgrade DB to %1: SQL', [1 => $rev]), 'runSql', $rev); + } + + public function upgrade_5_74_beta1($rev): void { + $this->addTask(ts('Upgrade DB to %1: SQL', [1 => $rev]), 'runSql', $rev); + } + +} diff --git a/www/modules/civicrm/CRM/Upgrade/Incremental/php/FiveSeventyOne.php b/www/modules/civicrm/CRM/Upgrade/Incremental/php/FiveSeventyOne.php new file mode 100644 index 000000000..212a14b29 --- /dev/null +++ b/www/modules/civicrm/CRM/Upgrade/Incremental/php/FiveSeventyOne.php @@ -0,0 +1,60 @@ +addTask(ts('Upgrade DB to %1: SQL', [1 => $rev]), 'runSql', $rev); + + $this->addTask('Add entity_delete column', 'addColumn', 'civicrm_custom_field', 'fk_entity_on_delete', "VARCHAR(255) NOT NULL DEFAULT 'set_null' COMMENT 'Behavior if referenced entity is deleted.' AFTER `fk_entity`"); + + $this->addTask('Make civicrm_uf_group.name required', 'alterColumn', 'civicrm_uf_group', 'name', + "varchar(64) NOT NULL COMMENT 'Form name.'" + ); + $this->addTask('Make civicrm_uf_group.frontend_title required', 'alterColumn', 'civicrm_uf_group', 'frontend_title', + "varchar(64) NOT NULL COMMENT 'Profile Form Public title'", + TRUE + ); + } + + /** + * Upgrade step; adds tasks including 'runSql'. + * + * @param string $rev + * The version number matching this function name + */ + public function upgrade_5_71_beta1(string $rev): void { + $this->addTask(ts('Upgrade DB to %1: SQL', [1 => $rev]), 'runSql', $rev); + $this->addTask('Make civicrm_event.selfcancelxfer_time', 'alterColumn', 'civicrm_event', 'selfcancelxfer_time', + "int(11) DEFAULT 0 NOT NULL COMMENT 'Number of hours prior to event start date to allow self-service cancellation or transfer.'" + ); + } + +} diff --git a/www/modules/civicrm/CRM/Upgrade/Incremental/php/FiveSeventyThree.php b/www/modules/civicrm/CRM/Upgrade/Incremental/php/FiveSeventyThree.php new file mode 100644 index 000000000..5f249ce16 --- /dev/null +++ b/www/modules/civicrm/CRM/Upgrade/Incremental/php/FiveSeventyThree.php @@ -0,0 +1,34 @@ +addTask(ts('Upgrade DB to %1: SQL', [1 => $rev]), 'runSql', $rev); + } + +} diff --git a/www/modules/civicrm/CRM/Upgrade/Incremental/php/FiveSeventyTwo.php b/www/modules/civicrm/CRM/Upgrade/Incremental/php/FiveSeventyTwo.php new file mode 100644 index 000000000..1b4f4bd76 --- /dev/null +++ b/www/modules/civicrm/CRM/Upgrade/Incremental/php/FiveSeventyTwo.php @@ -0,0 +1,61 @@ +addTask(ts('Upgrade DB to %1: SQL', [1 => $rev]), 'runSql', $rev); + $this->addTask('Remove localized suffixes from civicrm_mailing_group.entity_table', 'fixMailingGroupEntityTable'); + $this->addTask('Replace displayName smarty token in UFNotify subject', + 'updateMessageToken', 'uf_notify', 'ts 1=$displayName', 'ts 1=$userDisplayName', $rev + ); + $this->addTask('Replace displayName smarty token in UFNotify', + 'updateMessageToken', 'uf_notify', '$displayName', 'contact.display_name', $rev + ); + $this->addTask('Replace currentDate smarty token in UFNotify', + 'updateMessageToken', 'uf_notify', '$currentDate', 'domain.now|crmDate:"Full"', $rev + ); + $this->addTask('Add last_run_end column to Job table', 'addColumn', 'civicrm_job', 'last_run_end', + 'timestamp NULL DEFAULT NULL COMMENT "When did this cron entry last finish running"'); + } + + /** + * Remove unwanted dbLocale suffixes from values in civicrm_mailing_group.entity_table. + * + * @see https://github.com/civicrm/civicrm-core/pull/29366 + * + * @return bool + */ + public static function fixMailingGroupEntityTable(): bool { + $updateQuery = 'UPDATE civicrm_mailing_group SET entity_table = "civicrm_mailing" WHERE entity_table LIKE "civicrm_mailing_%"'; + CRM_Core_DAO::executeQuery($updateQuery, [], TRUE, NULL, FALSE, FALSE); + $updateQuery = 'UPDATE civicrm_mailing_group SET entity_table = "civicrm_group" WHERE entity_table LIKE "civicrm_group_%"'; + CRM_Core_DAO::executeQuery($updateQuery, [], TRUE, NULL, FALSE, FALSE); + return TRUE; + } + +} diff --git a/www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.71.alpha1.mysql.tpl b/www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.71.alpha1.mysql.tpl new file mode 100644 index 000000000..ec39fc1a1 --- /dev/null +++ b/www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.71.alpha1.mysql.tpl @@ -0,0 +1,22 @@ +{* file to handle db changes in 5.71.alpha1 during upgrade *} + +-- Add name field, make frontend_title required (in conjunction with php function) +{if $multilingual} + {foreach from=$locales item=locale} + UPDATE `civicrm_uf_group` + SET `frontend_title_{$locale}` = `title_{$locale}` + WHERE `frontend_title_{$locale}` IS NULL OR `frontend_title_{$locale}` = ''; + + UPDATE `civicrm_uf_group` + SET `name` = CONCAT(`title_{$locale}`, `id`) + WHERE `name` IS NULL OR `name` = ''; + {/foreach} +{else} + UPDATE `civicrm_uf_group` + SET `frontend_title` = `title` + WHERE `frontend_title` IS NULL OR `frontend_title` = ''; + + UPDATE `civicrm_uf_group` + SET `name` = CONCAT(`title`, `id`) + WHERE `name` IS NULL OR `name` = ''; +{/if} diff --git a/www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.71.beta1.mysql.tpl b/www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.71.beta1.mysql.tpl new file mode 100644 index 000000000..797abb3cc --- /dev/null +++ b/www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.71.beta1.mysql.tpl @@ -0,0 +1,2 @@ +{* file to handle db changes in 5.72.alpha1 during upgrade *} +UPDATE civicrm_event SET selfcancelxfer_time = 0 WHERE selfcancelxfer_time IS NULL; diff --git a/www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.72.alpha1.mysql.tpl b/www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.72.alpha1.mysql.tpl new file mode 100644 index 000000000..4bcec6660 --- /dev/null +++ b/www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.72.alpha1.mysql.tpl @@ -0,0 +1 @@ +{* file to handle db changes in 5.72.alpha1 during upgrade *} diff --git a/www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.73.alpha1.mysql.tpl b/www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.73.alpha1.mysql.tpl new file mode 100644 index 000000000..be4883b7a --- /dev/null +++ b/www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.73.alpha1.mysql.tpl @@ -0,0 +1 @@ +{* file to handle db changes in 5.73.alpha1 during upgrade *} diff --git a/www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.74.alpha1.mysql.tpl b/www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.74.alpha1.mysql.tpl new file mode 100644 index 000000000..e60293d06 --- /dev/null +++ b/www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.74.alpha1.mysql.tpl @@ -0,0 +1,3 @@ +{* file to handle db changes in 5.74.alpha1 during upgrade *} +UPDATE civicrm_navigation SET url = 'civicrm/import/participant?reset=1' +WHERE url = 'civicrm/event/import?reset=1'; diff --git a/www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.74.beta1.mysql.tpl b/www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.74.beta1.mysql.tpl new file mode 100644 index 000000000..455d05847 --- /dev/null +++ b/www/modules/civicrm/CRM/Upgrade/Incremental/sql/5.74.beta1.mysql.tpl @@ -0,0 +1,3 @@ +{* file to handle db changes in 5.74.alpha1 during upgrade *} +UPDATE civicrm_navigation SET url = 'civicrm/import/membership?reset=1' +WHERE url = 'civicrm/member/import?reset=1'; diff --git a/www/modules/civicrm/CRM/Utils/Array.php b/www/modules/civicrm/CRM/Utils/Array.php index b4aad583b..89d66beb2 100644 --- a/www/modules/civicrm/CRM/Utils/Array.php +++ b/www/modules/civicrm/CRM/Utils/Array.php @@ -532,7 +532,9 @@ public static function crmArraySortByField($array, $field) { $fields = (array) $field; uasort($array, function ($a, $b) use ($fields) { foreach ($fields as $f) { - $v = strnatcmp($a[$f], $b[$f]); + $f1 = $a[$f] ?? ''; + $f2 = $b[$f] ?? ''; + $v = strnatcmp($f1, $f2); if ($v !== 0) { return $v; } diff --git a/www/modules/civicrm/CRM/Utils/Cache/Tiered.php b/www/modules/civicrm/CRM/Utils/Cache/Tiered.php index 538809966..b71450031 100644 --- a/www/modules/civicrm/CRM/Utils/Cache/Tiered.php +++ b/www/modules/civicrm/CRM/Utils/Cache/Tiered.php @@ -164,6 +164,9 @@ protected function getEffectiveTtl($tierNum, $ttl) { return $this->maxTimeouts[$tierNum]; } else { + if ($ttl instanceof \DateInterval) { + $ttl = date_add(new DateTime(), $ttl)->getTimestamp() - time(); + } return min($this->maxTimeouts[$tierNum], $ttl); } } diff --git a/www/modules/civicrm/CRM/Utils/Check/Component/Env.php b/www/modules/civicrm/CRM/Utils/Check/Component/Env.php index 72427d69f..4c4f896b3 100644 --- a/www/modules/civicrm/CRM/Utils/Check/Component/Env.php +++ b/www/modules/civicrm/CRM/Utils/Check/Component/Env.php @@ -246,16 +246,16 @@ public function checkDomainNameEmail($force = FALSE) { * @param bool $force * @return CRM_Utils_Check_Message[] */ - public function checkDefaultMailbox($force = FALSE) { + public function checkDefaultMailbox($force = FALSE): array { $messages = []; // CiviMail doesn't work in non-production environments; skip. - if (!$force && CRM_Core_Config::environment() != 'Production') { + if (!$force && CRM_Core_Config::environment() !== 'Production') { return $messages; } if (CRM_Core_Component::isEnabled('CiviMail') && - CRM_Core_BAO_MailSettings::defaultDomain() == "EXAMPLE.ORG" + CRM_Core_BAO_MailSettings::defaultDomain() === "EXAMPLE.ORG" ) { $message = new CRM_Utils_Check_Message( __FUNCTION__, @@ -1120,21 +1120,60 @@ public function checkMysqlVersion() { return $messages; } - public function checkPHPIntlExists() { + public function checkPHPIntlExists(): array { $messages = []; if (!extension_loaded('intl')) { $messages[] = new CRM_Utils_Check_Message( __FUNCTION__, ts('This system currently does not have the PHP-Intl extension enabled. Please contact your system administrator about getting the extension enabled.'), ts('Missing PHP Extension: INTL'), - \Psr\Log\LogLevel::WARNING, + LogLevel::WARNING, 'fa-server' ); } return $messages; } - public function checkAngularModuleSettings() { + /** + * Let people know if they could improve performance by defining the exclude directory. + * + * @return array + */ + public function checkExcludeDirectories(): array { + $messages = []; + if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN' && !defined('CIVICRM_EXCLUDE_DIRS_PATTERN')) { + $messages[] = new CRM_Utils_Check_Message( + __FUNCTION__, + ts('You can improve performance by defining the CIVICRM_EXCLUDE_DIRS_PATTERN in your civicrm.settings.php file.') . '
' . CRM_Utils_System::docURL2('sysadmin/setup/optimizations/#exclude-dirs-that-do-not-need-to-be-scanned'), + ts('Performance can be improved by configuring the exclude directories path'), + LogLevel::NOTICE, + 'fa-flag' + ); + } + return $messages; + } + + /** + * Let people know if they could improve performance by defining the template compile check. + * + * @return array + */ + public function checkTemplateCompileCheck(): array { + $messages = []; + $isProbablyDevelopmentSite = str_contains(CIVICRM_UF_BASEURL, 'localhost') || str_contains(CIVICRM_UF_BASEURL, 'staging') || str_contains(CIVICRM_UF_BASEURL, 'dev'); + if (!defined('CIVICRM_TEMPLATE_COMPILE_CHECK') && !$isProbablyDevelopmentSite && !\Civi::settings()->get('debug_enabled')) { + $messages[] = new CRM_Utils_Check_Message( + __FUNCTION__, + ts('You can improve performance on production sites by specifying the CIVICRM_TEMPLATE_COMPILE_CHECK in the civicrm.settings.php file.') . '
' . CRM_Utils_System::docURL2('sysadmin/setup/optimizations/#disable-compile-check'), + ts('Performance can be improved on live sites by defining the template compile check'), + LogLevel::NOTICE, + 'fa-flag' + ); + } + return $messages; + } + + public function checkAngularModuleSettings(): array { $messages = []; $modules = Civi::container()->get('angular')->getModules(); foreach ($modules as $name => $module) { @@ -1147,7 +1186,7 @@ public function checkAngularModuleSettings() { 3 => 'target="_blank" href="https://github.com/civicrm/civicrm-core/pull/19052"', ]), ts('Unsupported Angular Setting'), - \Psr\Log\LogLevel::WARNING, + LogLevel::WARNING, 'fa-code' ); } @@ -1155,7 +1194,7 @@ public function checkAngularModuleSettings() { return $messages; } - public function checkForMultipleL10NDirs() { + public function checkForMultipleL10NDirs(): array { $messages = []; $dirs = []; @@ -1191,7 +1230,7 @@ public function checkForMultipleL10NDirs() { ts('There are multiple l10n directories, listed below. The one that appears to be in use is %1. You may wish to remove the others to avoid confusion when updating translation files.', [1 => $current_l10n]) . '

    ' . $dirlist . '

', ts('Multiple l10n Directories'), - \Psr\Log\LogLevel::WARNING, + LogLevel::WARNING, 'fa-files-o' ); } @@ -1200,8 +1239,12 @@ public function checkForMultipleL10NDirs() { /** * Avoid issues with trailing slashes and mixed separators on windows. + * + * @param string $path + * + * @return string */ - private static function normalizePath($path) { + private static function normalizePath($path): string { return rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/'); } diff --git a/www/modules/civicrm/CRM/Utils/Check/Component/Smarty.php b/www/modules/civicrm/CRM/Utils/Check/Component/Smarty.php index 8e742c97d..01c7c5a4e 100644 --- a/www/modules/civicrm/CRM/Utils/Check/Component/Smarty.php +++ b/www/modules/civicrm/CRM/Utils/Check/Component/Smarty.php @@ -23,25 +23,21 @@ class CRM_Utils_Check_Component_Smarty extends CRM_Utils_Check_Component { * * @return CRM_Utils_Check_Message[] */ - public function checkSmarty3(): array { - $p = function($text) { - return '

' . $text . '

'; - }; - + public function checkSmartyVersion(): array { $messages = []; - if (!defined('CIVICRM_SMARTY3_AUTOLOAD_PATH')) { - $smartyPath = \Civi::paths()->getPath('[civicrm.packages]/smarty3/vendor/autoload.php'); + if (!defined('CIVICRM_SMARTY_AUTOLOAD_PATH') && !defined('CIVICRM_SMARTY3_AUTOLOAD_PATH')) { + $smartyPath = \Civi::paths()->getPath('[civicrm.packages]/smarty4/vendor/autoload.php'); $messages[] = new CRM_Utils_Check_Message( __FUNCTION__, - $p(ts('CiviCRM is updating a major library (Smarty) to improve performance and security. In CiviCRM v5.69, the update is optional, but you should try it now.')) - . $p(ts('To apply the update, add this statement to civicrm.settings.php:')) - . sprintf("
  define('CIVICRM_SMARTY3_AUTOLOAD_PATH',\n    %s);
", htmlentities(var_export($smartyPath, 1))) - . $p('Some extensions may not work yet with Smarty v3. If you encounter problems, then simply remove the statement.') - . $p(ts('Upcoming versions will standardize on Smarty v3. CiviCRM v5.69-ESR will provide extended support for Smarty v2. To learn more and discuss, see the Smarty transition page.', [ + '

' . (ts('CiviCRM is updating a major library (Smarty) to improve performance and security and php 8.3 compatibility. The update is currently optional, but you should try it now.')) . '

' + . '

' . (ts('To apply the update, add this statement to civicrm.settings.php:')) + . sprintf("

  define('CIVICRM_SMARTY_AUTOLOAD_PATH',\n    %s);
", htmlentities(var_export($smartyPath, 1))) . '

' + . '

' . ('Some extensions may not work yet with Smarty v4. If you encounter problems, then simply remove the statement.') . '

' + . '

' . (ts('Upcoming versions will standardize on Smarty v4. CiviCRM v5.69-ESR will provide extended support for Smarty v2. To learn more and discuss, see the Smarty transition page.' . '

', [ 1 => 'target="_blank" href="' . htmlentities('https://civicrm.org/esr') . '"', 2 => 'target="_blank" href="' . htmlentities('https://civicrm.org/redirect/smarty-v3') . '"', ])), - ts('Smarty Update (v2 => v3)'), + ts('Smarty Update (v2 => v4)'), LogLevel::WARNING, 'fa-lock' ); diff --git a/www/modules/civicrm/CRM/Utils/Check/Component/Source.php b/www/modules/civicrm/CRM/Utils/Check/Component/Source.php index 4af45ff20..803aac50e 100644 --- a/www/modules/civicrm/CRM/Utils/Check/Component/Source.php +++ b/www/modules/civicrm/CRM/Utils/Check/Component/Source.php @@ -31,7 +31,10 @@ public function findOrphanedFiles() { $orphans = []; foreach ($this->getRemovedFiles() as $file) { $path = Civi::paths()->getPath("[civicrm.root]/$file"); - if (file_exists(rtrim($path, '/*'))) { + $path = rtrim($path, '/*'); + // On case-insensitive filesystems we need to do some more work + $actualPath = $this->findCorrectCaseForFile($path); + if ($actualPath !== NULL) { $orphans[] = [ 'name' => $file, 'path' => $path, @@ -64,4 +67,25 @@ public function checkOrphans() { return $messages; } + /** + * Linux is case sensitive, so this will be a no-op. + * Windows is case insensitive, Mac is usually insensitive but sometimes + * sensitive. + * Note that realpath() will return the real casing for a file on windows, + * but not on mac, so we need a different method. glob returns the real + * casing, but means we need to loop. + * + * @param string $path + * @return string|null + */ + private function findCorrectCaseForFile(string $path): ?string { + $fileToFind = basename($path); + foreach (glob(dirname($path) . '/*', GLOB_NOSORT) as $theRealFile) { + if ($fileToFind === basename($theRealFile)) { + return $theRealFile; + } + } + return NULL; + } + } diff --git a/www/modules/civicrm/CRM/Utils/Check/Message.php b/www/modules/civicrm/CRM/Utils/Check/Message.php index deb4a4b6c..345737e7a 100644 --- a/www/modules/civicrm/CRM/Utils/Check/Message.php +++ b/www/modules/civicrm/CRM/Utils/Check/Message.php @@ -152,12 +152,12 @@ public function addHelp($help) { * @param string|false $confirmation * Optional confirmation message before performing action * @param string $type - * Currently supports: api3 or href + * Link action type. One of: href|api3|api4 * @param array $params - * Params to be passed to CRM.api3 or CRM.url depending on type - * Ex: ['MyApiEntity', 'MyApiAction', [...params...]] - * Ex: ['path' => 'civicrm/admin/foo', 'query' => 'reset=1'] - * Ex: ['url' => 'https://example.com/more/info'] + * Params to be passed to the api or CRM.url (depending on $type) + * Ex (api4): ['MyApiEntity', 'MyApiAction', [...apiParams...]] + * Ex (href): ['path' => 'civicrm/admin/foo', 'query' => 'reset=1'] + * Ex (href): ['url' => 'https://example.com/more/info'] * @param string $icon * Fa-icon class for the button */ diff --git a/www/modules/civicrm/CRM/Utils/Date.php b/www/modules/civicrm/CRM/Utils/Date.php index de3943818..c03cdc5b4 100644 --- a/www/modules/civicrm/CRM/Utils/Date.php +++ b/www/modules/civicrm/CRM/Utils/Date.php @@ -251,7 +251,7 @@ public static function getAvailableInputFormats(bool $isShowTime): array { CRM_Utils_Date::DATE_mm_dd_yyyy => ts('mm/dd/yyyy OR mm-dd-yyyy (12/25/1998 OR 12-25-1998) OR (9/1/2008 OR 9-1-2008)'), CRM_Utils_Date::DATE_Month_dd_yyyy => ts('Month dd, yyyy (December 12, 1998)'), CRM_Utils_Date::DATE_dd_mon_yy => ts('dd-mon-yy OR dd/mm/yy (25-Dec-98 OR 25/12/98 OR 1-09-98)'), - CRM_Utils_Date::DATE_dd_mm_yyyy => ts('dd/mm/yyyy (25/12/1998 OR 1/9/2008 OR 1-Sep-2008)'), + CRM_Utils_Date::DATE_dd_mm_yyyy => ts('dd/mm/yyyy (25/12/1998 OR 1/9/2008 OR 1-Sep-2008 or 25.12.1998 or 25-12-1998)'), ]; } @@ -761,10 +761,10 @@ protected static function validateDateInput(string $inputValue, int $dateType = return preg_match('/^\d\d\d\d-?(\d|\d\d)-?(\d|\d\d)$/', $inputValue); case self::DATE_mm_dd_yy: - return preg_match('/^(\d|\d\d)[-\/](\d|\d\d)[-\/]\d\d$/', $inputValue); + return preg_match('/^(\d|\d\d)[-\/.](\d|\d\d)[-\/.]\d\d$/', $inputValue); case self::DATE_mm_dd_yyyy: - return preg_match('/^(\d|\d\d)[-\/](\d|\d\d)[-\/]\d\d\d\d$/', $inputValue); + return preg_match('/^(\d|\d\d)[-\/.](\d|\d\d)[-\/,]\d\d\d\d$/', $inputValue); case self::DATE_Month_dd_yyyy: $monthRegex = self::getMonthRegex(); @@ -775,7 +775,7 @@ protected static function validateDateInput(string $inputValue, int $dateType = return preg_match('/^(\d|\d\d)-' . self::getMonthRegex() . '-\d\d$/i', $inputValue) || preg_match('/^(\d|\d\d)[-\/](\d|\d\d)[-\/]\d\d$/', $inputValue); case self::DATE_dd_mm_yyyy: - return preg_match('/^(\d|\d\d)[-\/](\d|\d\d)[-\/]\d\d\d\d/', $inputValue); + return preg_match('/^(\d|\d\d)[-\/.](\d|\d\d)[-\/.]\d\d\d\d/', $inputValue); } return FALSE; } @@ -2215,13 +2215,13 @@ public static function formatDate($date, int $dateType = self::DATE_yyyy_mm_dd): // PHP interprets slashes as American and dots/dashes as European/other. // The only thing we support for mm_dd_yy that differs from strtotime is // the use of dashes - so here we replace then we can use strtotime. - $date = str_replace('-', '/', $date); + $date = str_replace(['-', '.'], '/', $date); $date = self::replaceShortYear($date, '/', 3); } if ($dateType === self::DATE_dd_mon_yy || $dateType === self::DATE_dd_mm_yyyy) { // PHP interprets slashes as American and dashes as European/other // We swap any slashes to dashes so strtotime will handle. - $date = str_replace('/', '-', $date); + $date = str_replace(['/', '.'], '-', $date); $date = self::replaceTextMonth($date, '-', 2); $date = self::replaceShortYear($date, '-', 3); } diff --git a/www/modules/civicrm/CRM/Utils/Hook.php b/www/modules/civicrm/CRM/Utils/Hook.php index 7a9e2aa79..33394d572 100644 --- a/www/modules/civicrm/CRM/Utils/Hook.php +++ b/www/modules/civicrm/CRM/Utils/Hook.php @@ -438,7 +438,7 @@ public static function postCommit($op, $objectName, $objectId, $objectRef = NULL * @return null * the return value is ignored */ - public static function links($op, $objectName, &$objectId, &$links, &$mask = NULL, &$values = []) { + public static function links($op, $objectName, $objectId, &$links, &$mask = NULL, &$values = []) { return self::singleton()->invoke(['op', 'objectName', 'objectId', 'links', 'mask', 'values'], $op, $objectName, $objectId, $links, $mask, $values, 'civicrm_links'); } @@ -649,7 +649,7 @@ public static function aclGroup($type, $contactID, $tableName, &$allGroups, &$cu * Values from WHERE or ON clause */ public static function selectWhereClause($entity, array &$clauses, int $userId = NULL, array $conditions = []): void { - $entityName = is_object($entity) ? CRM_Core_DAO_AllCoreTables::getBriefName(get_class($entity)) : $entity; + $entityName = is_object($entity) ? CRM_Core_DAO_AllCoreTables::getEntityNameForClass(get_class($entity)) : $entity; $null = NULL; $userId ??= (int) CRM_Core_Session::getLoggedInContactID(); self::singleton()->invoke(['entity', 'clauses', 'userId', 'conditions'], @@ -975,6 +975,8 @@ public static function alterAdminPanel(&$panels) { * @param string $className * The top level className from where the hook is invoked. * + * @deprecated since 5.71 will be removed sometime after all core uses are fully removed. + * * @return null */ public static function tokenValues( @@ -1617,21 +1619,25 @@ public static function export(&$exportTempTable, &$headerRows, &$sqlColumns, $ex /** * This hook allows modification of the queries constructed from dupe rules. * + * @deprecated since 5.72 + * * @param string $obj * Object of rulegroup class. * @param string $type * Type of queries e.g table / threshold. * @param array $query * Set of queries. - * - * @return mixed */ public static function dupeQuery($obj, $type, &$query) { $null = NULL; - return self::singleton()->invoke(['obj', 'type', 'query'], $obj, $type, $query, + $original = $query; + self::singleton()->invoke(['obj', 'type', 'query'], $obj, $type, $query, $null, $null, $null, 'civicrm_dupeQuery' ); + if ($original !== $query && $type !== 'supportedFields') { + CRM_Core_Error::deprecatedWarning('hook_civicrm_dupeQuery is deprecated.'); + } } /** @@ -2310,6 +2316,7 @@ public static function cron($jobManager) { * * @return null * The return value is ignored + * @throws RuntimeException */ public static function permission(&$newPermissions) { $null = NULL; diff --git a/www/modules/civicrm/CRM/Utils/ICalendar.php b/www/modules/civicrm/CRM/Utils/ICalendar.php index 6cafb774e..4564be546 100644 --- a/www/modules/civicrm/CRM/Utils/ICalendar.php +++ b/www/modules/civicrm/CRM/Utils/ICalendar.php @@ -174,16 +174,17 @@ public static function generate_timezones(array $timezones, $date_min, $date_max $tz_items = []; foreach ($timezones as $tzstr) { - $timezone = new DateTimeZone($tzstr); + $utcTimezone = new DateTimeZone('UTC'); + $eventTimezone = new DateTimeZone($tzstr); - $transitions = $timezone->getTransitions($date_min, $date_max); + $transitions = $eventTimezone->getTransitions($date_min, $date_max); if (count($transitions) === 1) { $transitions[] = array_values($transitions)[0]; } $item = [ - 'id' => $timezone->getName(), + 'id' => $eventTimezone->getName(), 'transitions' => [], ]; @@ -195,9 +196,8 @@ public static function generate_timezones(array $timezones, $date_min, $date_max 'offset_from' => self::format_tz_offset($last_transition['offset']), 'offset_to' => self::format_tz_offset($transition['offset']), 'abbr' => $transition['abbr'], - 'dtstart' => date_create($transition['time'], $timezone)->format("Ymd\THis"), + 'dtstart' => date_create($transition['time'], $utcTimezone)->setTimezone($eventTimezone)->format("Ymd\THis"), ]; - $last_transition = $transition; } diff --git a/www/modules/civicrm/CRM/Utils/JS.php b/www/modules/civicrm/CRM/Utils/JS.php index 6b5c03735..2c4705c70 100644 --- a/www/modules/civicrm/CRM/Utils/JS.php +++ b/www/modules/civicrm/CRM/Utils/JS.php @@ -163,18 +163,15 @@ public static function decode($js, $throwException = FALSE) { public static function convertSingleQuoteString(string $str, $throwException) { // json_decode can only handle double quotes around strings, so convert single-quoted strings $backslash = chr(0) . 'backslash' . chr(0); - $str = str_replace(['\\\\', '\\"', '"', '\\&', '\\/', $backslash], [$backslash, '"', '\\"', '&', '/', '\\'], substr($str, 1, -1)); + $str = str_replace(['\\\\', '\\"', '"', '\\&', '\\/'], [$backslash, '"', '\\"', '&', '/'], substr($str, 1, -1)); // Ensure the string doesn't terminate early by checking that all single quotes are escaped - $pos = -1; - while (($pos = strpos($str, "'", $pos + 1)) !== FALSE) { - if (($pos - strlen(rtrim(substr($str, 0, $pos)))) % 2) { - if ($throwException) { - throw new CRM_Core_Exception('Invalid string passed to CRM_Utils_JS::decode'); - } - return NULL; + if (preg_match("/[^\\\\]'/", $str)) { + if ($throwException) { + throw new CRM_Core_Exception('Invalid string passed to CRM_Utils_JS::decode'); } + return NULL; } - return '"' . $str . '"'; + return '"' . str_replace(["\\'", $backslash], ["'", '\\\\'], $str) . '"'; } /** @@ -310,8 +307,8 @@ public static function writeObject($obj, $encodeValues = FALSE) { if ($brackets[0] == '{') { // Enclose the key in quotes unless it is purely alphanumeric if (preg_match('/\W/', $key)) { - // Prefer single quotes - $key = preg_match('/^[\w "]+$/', $key) ? "'" . $key . "'" : json_encode($key, JSON_UNESCAPED_SLASHES); + // Prefer single quotes around keys + $key = self::encode($key); } $js[] = "$key: $val"; } diff --git a/www/modules/civicrm/CRM/Utils/Mail.php b/www/modules/civicrm/CRM/Utils/Mail.php index e58a79af8..62ca313ff 100644 --- a/www/modules/civicrm/CRM/Utils/Mail.php +++ b/www/modules/civicrm/CRM/Utils/Mail.php @@ -169,14 +169,6 @@ public static function _createMailer($driver, $params) { * TRUE if a mail was sent, else FALSE. */ public static function send(array &$params): bool { - $defaultReturnPath = CRM_Core_BAO_MailSettings::defaultReturnPath(); - $includeMessageId = CRM_Core_BAO_MailSettings::includeMessageId(); - $emailDomain = CRM_Core_BAO_MailSettings::defaultDomain(); - $from = $params['from'] ?? NULL; - if (!$defaultReturnPath) { - $defaultReturnPath = self::pluckEmailFromHeader($from); - } - // first call the mail alter hook CRM_Utils_Hook::alterMailParams($params, 'singleEmail'); @@ -185,6 +177,121 @@ public static function send(array &$params): bool { return FALSE; } + list($headers, $message) = self::setEmailHeaders($params); + + $to = [$params['toEmail']]; + $mailer = \Civi::service('pear_mail'); + + // CRM-3795, CRM-7355, CRM-7557, CRM-9058, CRM-9887, CRM-12883, CRM-19173 and others ... + // The PEAR library requires different parameters based on the mailer used: + // * Mail_mail requires the Cc/Bcc recipients listed ONLY in the $headers variable + // * All other mailers require that all be recipients be listed in the $to array AND that + // the Bcc must not be present in $header as otherwise it will be shown to all recipients + // ref: https://pear.php.net/bugs/bug.php?id=8047, full thread and answer [2011-04-19 20:48 UTC] + // TODO: Refactor this quirk-handler as another filter in FilteredPearMailer. But that would merit review of impact on universe. + $driver = ($mailer instanceof CRM_Utils_Mail_FilteredPearMailer) ? $mailer->getDriver() : NULL; + $isPhpMail = (get_class($mailer) === "Mail_mail" || $driver === 'mail'); + if (!$isPhpMail) { + // get emails from headers, since these are + // combination of name and email addresses. + if (!empty($headers['Cc'])) { + $to[] = $headers['Cc'] ?? NULL; + } + if (!empty($headers['Bcc'])) { + $to[] = $headers['Bcc'] ?? NULL; + unset($headers['Bcc']); + } + } + + if (is_object($mailer)) { + try { + $result = $mailer->send($to, $headers, $message); + } + catch (Exception $e) { + \Civi::log()->error('Mailing error: ' . $e->getMessage()); + CRM_Core_Session::setStatus(ts('Unable to send email. Please report this message to the site administrator'), ts('Mailing Error'), 'error'); + return FALSE; + } + if (is_a($result, 'PEAR_Error')) { + $message = self::errorMessage($mailer, $result); + // append error message in case multiple calls are being made to + // this method in the course of sending a batch of messages. + \Civi::log()->error('Mailing error: ' . $message); + CRM_Core_Session::setStatus(ts('Unable to send email. Please report this message to the site administrator'), ts('Mailing Error'), 'error'); + return FALSE; + } + // CRM-10699 + CRM_Utils_Hook::postEmailSend($params); + return TRUE; + } + return FALSE; + } + + /** + * Send a test email using the selected mailer. + * + * @param Mail $mailer + * @param array $params + * Params by reference. + * + * @return bool + * TRUE if a mail was sent, else FALSE. + */ + public static function sendTest($mailer, array &$params): bool { + CRM_Utils_Hook::alterMailParams($params, 'testEmail'); + $message = $params['text']; + $to = $params['toEmail']; + + list($headers, $message) = self::setEmailHeaders($params); + + $from = self::pluckEmailFromHeader($headers['From']); + + $testMailStatusMsg = ts('Sending test email.') . ':
' + . ts('From: %1', [1 => $from]) . '
' + . ts('To: %1', [1 => $to]) . '
'; + + $mailerName = $mailer->getDriver() ?? ''; + + try { + $mailer->send($to, $headers, $message); + + if (defined('CIVICRM_MAIL_LOG') && defined('CIVICRM_MAIL_LOG_AND_SEND')) { + $testMailStatusMsg .= '
' . ts('You have defined CIVICRM_MAIL_LOG_AND_SEND - mail will be logged.') . '

'; + } + if (defined('CIVICRM_MAIL_LOG') && !defined('CIVICRM_MAIL_LOG_AND_SEND')) { + CRM_Core_Session::setStatus($testMailStatusMsg . ts('You have defined CIVICRM_MAIL_LOG - no mail will be sent. Your %1 settings have not been tested.', [1 => strtoupper($mailerName)]), ts("Mail not sent"), "warning"); + } + else { + CRM_Core_Session::setStatus($testMailStatusMsg . ts('Your %1 settings are correct. A test email has been sent to your email address.', [1 => strtoupper($mailerName)]), ts("Mail Sent"), "success"); + } + } + catch (Exception $e) { + $result = $e; + Civi::log()->error($e->getMessage()); + $errorMessage = CRM_Utils_Mail::errorMessage($mailer, $result); + CRM_Core_Session::setStatus($testMailStatusMsg . ts('Oops. Your %1 settings are incorrect. No test mail has been sent.', [1 => strtoupper($mailerName)]) . $errorMessage, ts("Mail Not Sent"), "error"); + return FALSE; + } + return TRUE; + } + + /** + * Set email headers + * + * @param array $params + * + * @return array + * An array of the Headers and Message. + */ + public static function setEmailHeaders($params): array { + $defaultReturnPath = CRM_Core_BAO_MailSettings::defaultReturnPath(); + $includeMessageId = CRM_Core_BAO_MailSettings::includeMessageId(); + $emailDomain = CRM_Core_BAO_MailSettings::defaultDomain(); + $from = $params['from'] ?? NULL; + if (!$defaultReturnPath) { + $defaultReturnPath = self::pluckEmailFromHeader($from); + } + $htmlMessage = $params['html'] ?? FALSE; if (trim(CRM_Utils_String::htmlToText((string) $htmlMessage)) === '') { $htmlMessage = FALSE; @@ -199,6 +306,16 @@ public static function send(array &$params): bool { // This is copied from the Action Schedule send code. $textMessage = str_replace('&', '&', $textMessage); } + if (str_contains($textMessage, 'Undefined array key') || str_contains($htmlMessage, 'Undefined array key') || str_contains($htmlMessage, 'Undefined index')) { + $logCount = \Civi::$statics[__CLASS__][__FUNCTION__]['count'] ?? 0; + if ($logCount < 3) { + // Only record the first 3 times since there might be different messages but after 3 chances are + // it's just bulk run of the same.. + CRM_Core_Error::deprecatedWarning('email output affected by undefined php properties:' . (CRM_Utils_Constant::value('CIVICRM_UF') === 'UnitTests' ? CRM_Utils_String::purifyHTML($htmlMessage) : '')); + $logCount++; + \Civi::$statics[__CLASS__][__FUNCTION__]['count'] = $logCount; + } + } $headers = []; // CRM-10699 support custom email headers @@ -290,52 +407,7 @@ public static function send(array &$params): bool { $message = self::setMimeParams($msg); $headers = $msg->headers($headers); - $to = [$params['toEmail']]; - $mailer = \Civi::service('pear_mail'); - - // CRM-3795, CRM-7355, CRM-7557, CRM-9058, CRM-9887, CRM-12883, CRM-19173 and others ... - // The PEAR library requires different parameters based on the mailer used: - // * Mail_mail requires the Cc/Bcc recipients listed ONLY in the $headers variable - // * All other mailers require that all be recipients be listed in the $to array AND that - // the Bcc must not be present in $header as otherwise it will be shown to all recipients - // ref: https://pear.php.net/bugs/bug.php?id=8047, full thread and answer [2011-04-19 20:48 UTC] - // TODO: Refactor this quirk-handler as another filter in FilteredPearMailer. But that would merit review of impact on universe. - $driver = ($mailer instanceof CRM_Utils_Mail_FilteredPearMailer) ? $mailer->getDriver() : NULL; - $isPhpMail = (get_class($mailer) === "Mail_mail" || $driver === 'mail'); - if (!$isPhpMail) { - // get emails from headers, since these are - // combination of name and email addresses. - if (!empty($headers['Cc'])) { - $to[] = $headers['Cc'] ?? NULL; - } - if (!empty($headers['Bcc'])) { - $to[] = $headers['Bcc'] ?? NULL; - unset($headers['Bcc']); - } - } - - if (is_object($mailer)) { - try { - $result = $mailer->send($to, $headers, $message); - } - catch (Exception $e) { - \Civi::log()->error('Mailing error: ' . $e->getMessage()); - CRM_Core_Session::setStatus(ts('Unable to send email. Please report this message to the site administrator'), ts('Mailing Error'), 'error'); - return FALSE; - } - if (is_a($result, 'PEAR_Error')) { - $message = self::errorMessage($mailer, $result); - // append error message in case multiple calls are being made to - // this method in the course of sending a batch of messages. - \Civi::log()->error('Mailing error: ' . $message); - CRM_Core_Session::setStatus(ts('Unable to send email. Please report this message to the site administrator'), ts('Mailing Error'), 'error'); - return FALSE; - } - // CRM-10699 - CRM_Utils_Hook::postEmailSend($params); - return TRUE; - } - return FALSE; + return [$headers, $message]; } /** diff --git a/www/modules/civicrm/CRM/Utils/Mail/EmailProcessor.php b/www/modules/civicrm/CRM/Utils/Mail/EmailProcessor.php index edc5a3aeb..5170346e4 100644 --- a/www/modules/civicrm/CRM/Utils/Mail/EmailProcessor.php +++ b/www/modules/civicrm/CRM/Utils/Mail/EmailProcessor.php @@ -80,9 +80,9 @@ public static function processActivities() { private static function _process($civiMail, $dao, $is_create_activities) { // 0 = activities; 1 = bounce; $isBounceProcessing = $dao->is_default; - $targetFields = array_filter(explode(',', $dao->activity_targets)); - $assigneeFields = array_filter(explode(",", $dao->activity_assignees)); - $sourceFields = array_filter(explode(",", $dao->activity_source)); + $targetFields = array_filter(explode(',', (string) $dao->activity_targets)); + $assigneeFields = array_filter(explode(",", (string) $dao->activity_assignees)); + $sourceFields = array_filter(explode(",", (string) $dao->activity_source)); // create an array of all of to, from, cc, bcc that are in use for this Mail Account, so we don't create contacts for emails we aren't adding to the activity. $emailFields = array_merge($targetFields, $assigneeFields, $sourceFields); $createContact = !($dao->is_contact_creation_disabled_if_no_match) && !$isBounceProcessing; @@ -239,7 +239,7 @@ private static function _process($civiMail, $dao, $is_create_activities) { // get $replyTo from either the Reply-To header or from From // FIXME: make sure it works with Reply-Tos containing non-email stuff - $replyTo = $mail->getHeader('Reply-To') ? $mail->getHeader('Reply-To') : ($mail->from ? $mail->from->email : ""); + $replyTo = $mail->getHeader('Reply-To') ?: ($mail->from ? $mail->from->email : ""); // handle the action by passing it to the proper API call if (!empty($action)) { diff --git a/www/modules/civicrm/CRM/Utils/Money.php b/www/modules/civicrm/CRM/Utils/Money.php index 212386cf3..63ada48a9 100644 --- a/www/modules/civicrm/CRM/Utils/Money.php +++ b/www/modules/civicrm/CRM/Utils/Money.php @@ -100,7 +100,7 @@ public static function format($amount, $currency = NULL, $format = NULL, $onlyNu $replacements = [ '%a' => $amount, '%C' => $currency, - '%c' => CRM_Utils_Array::value($currency, self::$_currencySymbols, $currency), + '%c' => self::$_currencySymbols[$currency] ?? $currency, ]; return strtr($format, $replacements); } @@ -347,7 +347,9 @@ protected static function formatNumericByFormat($amount, $valueFormat = '%!i') { if (is_numeric($amount) && function_exists('money_format')) { $lc = setlocale(LC_MONETARY, 0); setlocale(LC_MONETARY, 'en_US.utf8', 'en_US', 'en_US.utf8', 'en_US', 'C'); + // phpcs:disable $amount = money_format($valueFormat, $amount); + // phpcs:enable setlocale(LC_MONETARY, $lc); } return $amount; diff --git a/www/modules/civicrm/CRM/Utils/REST.php b/www/modules/civicrm/CRM/Utils/REST.php index 6d245cc74..9e334bab4 100644 --- a/www/modules/civicrm/CRM/Utils/REST.php +++ b/www/modules/civicrm/CRM/Utils/REST.php @@ -390,7 +390,7 @@ public static function loadTemplate() { } $param = array_map('htmlentities', $_GET); unset($param['q']); - $smarty->assign_by_ref("request", $param); + $smarty->assign("request", $param); if (!self::isWebServiceRequest()) { @@ -631,6 +631,10 @@ public function loadCMSBootstrap() { * , ', + \Civi::url("iframe://$path")->addQuery($query), + $options['maxwidth'], + $options['maxheight'] + ), + 'width' => $options['maxwidth'], + 'height' => $options['maxheight'], + ]; + + // Allow other components to alter values like `title` or `provider_name`. + \Civi::dispatcher()->dispatch('hook_civicrm_oembed', \Civi\Core\Event\GenericHookEvent::create([ + 'oembed' => &$result, + 'request' => [ + 'route' => $route, + 'path' => $path, + 'query' => $query, + ], + ])); + + return $result; + } + + /** + * @param string $path + * @param array $query + * @param array $options + * Ex: ['maxwidth' => '102'] + * @return string + */ + public function createLinkTags(string $path, array $query = [], array $options = []): string { + $oembed = static::create($path, $query); + $query = $this->findPropagatedParams($query); + $url = \Civi::url('frontend://civicrm/oembed', 'a')->addQuery([ + 'url' => (string) \Civi::url('frontend://' . $path, 'a')->addQuery($query), + ]); + $url->addQuery(\CRM_Utils_Array::subset($options, ['maxwidth', 'maxheight'])); + + $buf = ''; + $buf .= sprintf("\n", + (clone $url)->addQuery('format=json'), + htmlentities($oembed['title']) + ); + $buf .= sprintf("\n", + (clone $url)->addQuery('format=xml'), + htmlentities($oembed['title']) + ); + return $buf; + } + + protected function normalizeOptions(array $options): array { + $result = $options; + $result['maxwidth'] = $this->applyPixelConstraint($result['maxwidth'] ?? $this->defaultWidth); + $result['maxheight'] = $this->applyPixelConstraint($result['maxheight'] ?? $this->defaultHeight); + return $result; + } + + private function applyPixelConstraint($value): int { + $value = \CRM_Utils_Type::validate($value, 'Integer'); + $value = max($value, $this->minPixels); + $value = min($value, $this->maxPixels); + return $value; + } + + /** + * Identify any query parameters that should be preserved/propagated to the equivalent oEmbed request. + * + * Note that support for this may vary by oEmbed client. When using -based discovery, + * some clients (eg WordPress) may interject with their preferred value of `url=XYZ`. + */ + public function findPropagatedParams(array $query): array { + $urlVar = \CRM_Core_Config::singleton()->userFrameworkURLVar; + $result = []; + foreach ($query as $key => $value) { + if ($key[0] !== '_' && !in_array($key, [$urlVar])) { + $result[$key] = $value; + } + } + return $result; + } + + /** + * Do we permit embedding of this route? + * + * @param string $path + * @return bool + */ + public function isAllowedRoute(string $path): bool { + return \Civi::service('iframe.router')->isAllowedRoute($path) && !preg_match(';^civicrm/(ajax|asset);', $path); + } + + public function getDefaultWidth(): int { + return $this->defaultWidth; + } + + public function getDefaultHeight(): int { + return $this->defaultHeight; + } + +} diff --git a/www/modules/civicrm/ext/oembed/Civi/Oembed/OembedDiscovery.php b/www/modules/civicrm/ext/oembed/Civi/Oembed/OembedDiscovery.php new file mode 100644 index 000000000..5007cb715 --- /dev/null +++ b/www/modules/civicrm/ext/oembed/Civi/Oembed/OembedDiscovery.php @@ -0,0 +1,68 @@ + ['onInvoke', 100], + ]; + } + + public function onInvoke(array $path) { + if ($_SERVER['REQUEST_METHOD'] !== 'GET') { + return; + } + + $pathStr = implode('/', $path); + $oembed = Civi::service('oembed'); + if (!$oembed->isAllowedRoute($pathStr)) { + return; + } + + // In standard discovery, any supported page (aka public pages) will include + // tag to advertise its support for oEmbed. + if (Civi::settings()->get('oembed_standard') || $pathStr === 'civicrm/share') { + $query = $oembed->findPropagatedParams($_GET); + \CRM_Core_Region::instance('html-header')->add([ + 'name' => 'oembed', + 'markup' => $oembed->createLinkTags($pathStr, $query), + ]); + } + + // In explicit sharing, the administrator sees a panel with options for sharing. + if (Civi::settings()->get('oembed_share') && \CRM_Core_Permission::check('administer oembed')) { + $query = $oembed->findPropagatedParams($_GET); + + $data = [ + 'page' => Civi::url('frontend://' . $pathStr, 'a')->addQuery($query), + 'iframe' => Civi::url('iframe://' . $pathStr, 'a')->addQuery($query), + 'share' => Civi::url('frontend://civicrm/share', 'a')->addQuery(['url' => Civi::url('frontend://' . $pathStr, 'a')->addQuery($query)]), + ]; + $options = [ + 'maxwidth' => Civi::service('oembed')->getDefaultWidth(), + 'maxheight' => Civi::service('oembed')->getDefaultHeight(), + ]; + + Civi::service('angularjs.loader')->addModules(['crmOembedSharing']); + \CRM_Core_Region::instance('page-footer')->add([ + 'name' => 'oembed-share', + 'markup' => sprintf(' ', + htmlentities(\CRM_Utils_JS::encode($data)), + htmlentities(\CRM_Utils_JS::encode($options)), + ), + ]); + } + } + +} diff --git a/www/modules/civicrm/ext/oembed/LICENSE.txt b/www/modules/civicrm/ext/oembed/LICENSE.txt new file mode 100644 index 000000000..cd2ae9c72 --- /dev/null +++ b/www/modules/civicrm/ext/oembed/LICENSE.txt @@ -0,0 +1,667 @@ +Package: oembed +Copyright (C) 2024, Tim Otten +Licensed under the GNU Affero Public License 3.0 (below). + +------------------------------------------------------------------------------- + + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/www/modules/civicrm/ext/oembed/README.md b/www/modules/civicrm/ext/oembed/README.md new file mode 100644 index 000000000..ce5d093c6 --- /dev/null +++ b/www/modules/civicrm/ext/oembed/README.md @@ -0,0 +1,30 @@ +# oembed (Developmental) + +The [oEmbed protocol](https://oembed.com/) allows you to share content between websites: + +> oEmbed is a format for allowing an embedded representation of a URL on +> third party sites. The simple API allows a website to display embedded +> content (such as photos or videos) when a user posts a link to that +> resource, without having to parse the resource directly. + +With this extension, CiviCRM acts as an oEmbed provider. It is under development. + +oEmbed providers and oEmbed clients exchange small snippets of HTML to represent the embedded content. +While the protocol theoretically allows many tags, prudent behavior on the open Internet is to limit +the range of tags. For content which needs rich information, the typical approach is to convey +an ` {/if}
-
-
+
+
diff --git a/www/modules/civicrm/templates/CRM/Mailing/Form/InsertTokens.tpl b/www/modules/civicrm/templates/CRM/Mailing/Form/InsertTokens.tpl index dc73e4166..18c8e84a5 100644 --- a/www/modules/civicrm/templates/CRM/Mailing/Form/InsertTokens.tpl +++ b/www/modules/civicrm/templates/CRM/Mailing/Form/InsertTokens.tpl @@ -25,7 +25,13 @@ var isMailing = false; text_message = "mailing_format"; isMailing = false; {/literal} -{elseif $form.formClass eq 'CRM_SMS_Form_Upload' || $form.formClass eq 'CRM_Contact_Form_Task_SMS'} +{elseif $form.formClass eq 'CRM_Contact_Form_Task_SMS'} + {literal} + prefix = "SMS"; + text_message = "sms_text_message"; + isMailing = true; + {/literal} +{elseif $form.formClass eq 'CRM_SMS_Form_Upload'} {literal} prefix = "SMS"; text_message = "sms_text_message"; @@ -74,7 +80,8 @@ function showSaveUpdateChkBox(prefix) { document.getElementById(prefix + "saveDetails").style.display = "none"; return; } - if (document.getElementById(prefix + "template") == null) { + + if (cj('#' + prefix + "template").val() === '') { if (document.getElementsByName(prefix + "saveTemplate")[0].checked){ document.getElementById(prefix + "saveDetails").style.display = "block"; document.getElementById(prefix + "editMessageDetails").style.display = "block"; @@ -93,7 +100,7 @@ function showSaveUpdateChkBox(prefix) { else if ( document.getElementsByName(prefix + "saveTemplate")[0].checked && document.getElementsByName(prefix + "updateTemplate")[0].checked ){ document.getElementById(prefix + "editMessageDetails").style.display = "block"; - document.getElementById(pefix + "saveDetails").style.display = "block"; + document.getElementById(prefix + "saveDetails").style.display = "block"; } else if ( document.getElementsByName(prefix + "saveTemplate")[0].checked == false && document.getElementsByName(prefix + "updateTemplate")[0].checked ) { @@ -102,7 +109,12 @@ function showSaveUpdateChkBox(prefix) { } else { document.getElementById(prefix + "saveDetails").style.display = "none"; - document.getElementById(prefix + "updateDetails").style.display = "none"; + if (cj('#' + prefix + "template").val() === '') { + document.getElementById(prefix + "updateDetails").style.display = "none"; + } + else { + document.getElementById(prefix + "updateDetails").style.display = "block"; + } } } @@ -113,20 +125,17 @@ function selectValue( val, prefix) { showSaveUpdateChkBox(prefix); } if ( !val ) { - if (document.getElementById("subject").length) { - document.getElementById("subject").value =""; - } - if (document.getElementById("subject").length) { - document.getElementById("subject").value =""; + // The SMS form has activity_subject not subject which is not + // cleared by this as subject is not present. It's unclear if that + // is deliberate or not but this does not error if the field is not present. + cj('#subject').val(''); + if (prefix === 'SMS') { + document.getElementById("sms_text_message").value =""; + return; } + if ( !isPDF ) { - if (prefix == 'SMS') { - document.getElementById("sms_text_message").value =""; - return; - } - else { - document.getElementById("text_message").value =""; - } + document.getElementById("text_message").value =""; } else { cj('.crm-html_email-accordion').show(); diff --git a/www/modules/civicrm/templates/CRM/Mailing/Page/Report.tpl b/www/modules/civicrm/templates/CRM/Mailing/Page/Report.tpl index a7bf50da8..ce763e86b 100644 --- a/www/modules/civicrm/templates/CRM/Mailing/Page/Report.tpl +++ b/www/modules/civicrm/templates/CRM/Mailing/Page/Report.tpl @@ -193,7 +193,7 @@ {ts}Mailing Name{/ts}{$report.mailing.name} {ts}Subject{/ts}{$report.mailing.subject} {ts}From{/ts}{$report.mailing.from_name} <{$report.mailing.from_email}> -{ts}Reply-to email{/ts}{$report.mailing.replyto_email|htmlentities} +{ts}Reply-to email{/ts}{$report.mailing.replyto_email|escape:'htmlall'} {ts}Forward replies{/ts}{if $report.mailing.forward_replies}{ts}Enabled{/ts}{else}{ts}Disabled{/ts}{/if} {ts}Auto-respond to replies{/ts}{if $report.mailing.auto_responder}{ts}Enabled{/ts}{else}{ts}Disabled{/ts}{/if} diff --git a/www/modules/civicrm/templates/CRM/Member/Form/Membership.tpl b/www/modules/civicrm/templates/CRM/Member/Form/Membership.tpl index 08f8bb334..85b28d778 100644 --- a/www/modules/civicrm/templates/CRM/Member/Form/Membership.tpl +++ b/www/modules/civicrm/templates/CRM/Member/Form/Membership.tpl @@ -48,7 +48,7 @@ {/if} {if $membershipMode}
- {ts 1=$displayName 2=$registerMode}Use this form to submit Membership Record on behalf of %1. A %2 transaction will be submitted using the selected payment processor.{/ts} + {ts 1=$displayName|escape 2=$registerMode}Use this form to submit Membership Record on behalf of %1. A %2 transaction will be submitted using the selected payment processor.{/ts}
{/if}
@@ -197,7 +197,7 @@ {/if} {$form.from_email_address.label} - {$form.from_email_address.html} {help id="id-from_email" file="CRM/Contact/Form/Task/Help/Email/id-from_email.hlp"} + {$form.from_email_address.html} {help id="id-from_email" file="CRM/Contact/Form/Task/Help/Email/id-from_email.hlp" title=$form.from_email_address.label} {$form.receipt_text.label} @@ -207,8 +207,8 @@ {include file="CRM/common/customDataBlock.tpl"} {if $accessContribution and $action eq 2 and $rows.0.contribution_id} -
-
{ts}Related Contributions{/ts}
+
+ {ts}Related Contributions{/ts}
{include file="CRM/Contribute/Form/Selector.tpl" context="Search"}
-
+ {/if} {if $softCredit} -
-
{ts}Related Soft Contributions{/ts}
+
+ {ts}Related Soft Contributions{/ts}
{include file="CRM/Contribute/Page/ContributionSoft.tpl" context="membership"}
-
+ {/if} {/if} @@ -590,7 +590,7 @@ showEmailOptions(); } - var customDataType = {/literal}{$customDataType|@json_encode}{literal}; + var customDataType = 'Membership'; // load form during form rule. {/literal}{if $buildPriceSet}{literal} @@ -605,7 +605,7 @@ var fname = '#priceset'; if ( !priceSetId ) { cj('#membership_type_id_1').val(0); - CRM.buildCustomData(customDataType, null); + CRM.buildCustomData('Membership', null); // hide price set fields. cj( fname ).hide( ); @@ -788,7 +788,7 @@ subTypeNames = null; } - CRM.buildCustomData(customDataType, subTypeNames); + CRM.buildCustomData('Membership', subTypeNames); } function enableAmountSection( setContributionType ) { diff --git a/www/modules/civicrm/templates/CRM/Member/Form/MembershipCommon.tpl b/www/modules/civicrm/templates/CRM/Member/Form/MembershipCommon.tpl index e8cc54938..e0e844a8c 100644 --- a/www/modules/civicrm/templates/CRM/Member/Form/MembershipCommon.tpl +++ b/www/modules/civicrm/templates/CRM/Member/Form/MembershipCommon.tpl @@ -3,7 +3,7 @@ - + diff --git a/www/modules/civicrm/templates/CRM/Member/Form/MembershipRenewal.tpl b/www/modules/civicrm/templates/CRM/Member/Form/MembershipRenewal.tpl index 932a25e7c..1fdcf35a7 100644 --- a/www/modules/civicrm/templates/CRM/Member/Form/MembershipRenewal.tpl +++ b/www/modules/civicrm/templates/CRM/Member/Form/MembershipRenewal.tpl @@ -21,7 +21,7 @@ {/if} {if $membershipMode}
- {ts 1=$displayName}Use this form to Renew Membership Record on behalf of %1.{/ts} + {ts 1=$displayName|escape}Use this form to renew membership on behalf of %1.{/ts} {if $registerMode == 'LIVE'} {ts}A LIVE transaction will be submitted using the selected payment processor.{/ts} {else} @@ -109,7 +109,7 @@
- + @@ -122,7 +122,7 @@
{$form.is_different_contribution_contact.label}{$form.is_different_contribution_contact.html}  {help id="id-contribution_contact"}{$form.is_different_contribution_contact.html}  {help id="id-contribution_contact" file="CRM/Member/Page/Tab.hlp"}
 
{$form.from_email_address.label}{$form.from_email_address.html} {help id="id-from_email" file="CRM/Contact/Form/Task/Help/Email/id-from_email.hlp"}{$form.from_email_address.html} {help id="id-from_email" file="CRM/Contact/Form/Task/Help/Email/id-from_email.hlp" title=$form.from_email_address.label}
{$form.receipt_text.label}
{/if} - {include file="CRM/common/customDataBlock.tpl"} + {include file="CRM/common/customDataBlock.tpl" groupID='' customDataType='Membership' cid=false}
{include file="CRM/common/formButtons.tpl" location="bottom"}
diff --git a/www/modules/civicrm/templates/CRM/Member/Form/MembershipView.tpl b/www/modules/civicrm/templates/CRM/Member/Form/MembershipView.tpl index b495f9ea3..62198e254 100644 --- a/www/modules/civicrm/templates/CRM/Member/Form/MembershipView.tpl +++ b/www/modules/civicrm/templates/CRM/Member/Form/MembershipView.tpl @@ -64,10 +64,10 @@ {include file="CRM/Custom/Page/CustomDataView.tpl"} {if $accessContribution} -
-
+
+ {ts}Related Contributions and Recurring Contributions{/ts} -
+
{if $rows.0.contribution_id} {include file="CRM/Contribute/Form/Selector.tpl" context="Search"} @@ -97,14 +97,14 @@
-
+ {/if} {if $softCredit} -
-
{ts}Related Soft Contributions{/ts}
+
+ {ts}Related Soft Contributions{/ts}
{include file="CRM/Contribute/Page/ContributionSoft.tpl" context="membership"}
-
+ {/if} {if $has_related} diff --git a/www/modules/civicrm/templates/CRM/Member/Form/Search.tpl b/www/modules/civicrm/templates/CRM/Member/Form/Search.tpl index 20b929411..16896dad4 100644 --- a/www/modules/civicrm/templates/CRM/Member/Form/Search.tpl +++ b/www/modules/civicrm/templates/CRM/Member/Form/Search.tpl @@ -8,10 +8,10 @@ +--------------------------------------------------------------------+ *}
-
-
+
+ {ts}Edit Search Criteria{/ts} -
+
{strip} @@ -23,8 +23,8 @@
{/strip} -
-
+
+
{if $rowsEmpty} diff --git a/www/modules/civicrm/templates/CRM/Member/Page/MembershipType.tpl b/www/modules/civicrm/templates/CRM/Member/Page/MembershipType.tpl index 8ca0e9dc0..0369b32fb 100644 --- a/www/modules/civicrm/templates/CRM/Member/Page/MembershipType.tpl +++ b/www/modules/civicrm/templates/CRM/Member/Page/MembershipType.tpl @@ -10,7 +10,7 @@ {capture assign=reminderLink}{crmURL p='civicrm/admin/scheduleReminders' q='reset=1'}{/capture} @@ -64,7 +64,7 @@ {else} {if $action ne 1}
- {ts}status{/ts} + {icon icon="fa-info-circle"}{/icon} {capture assign=crmURL}{crmURL p='civicrm/admin/member/membershipType/add' q="action=add&reset=1"}{/capture}{ts 1=$crmURL}There are no membership types entered. You can add one.{/ts}
{/if} diff --git a/www/modules/civicrm/templates/CRM/Pledge/Form/Pledge.tpl b/www/modules/civicrm/templates/CRM/Pledge/Form/Pledge.tpl index ef7fd58b0..3d4a3d923 100644 --- a/www/modules/civicrm/templates/CRM/Pledge/Form/Pledge.tpl +++ b/www/modules/civicrm/templates/CRM/Pledge/Form/Pledge.tpl @@ -96,7 +96,7 @@ {/if} {$form.from_email_address.label} - {$form.from_email_address.html} {help id="id-from_email" file="CRM/Contact/Form/Task/Help/Email/id-from_email.hlp"} + {$form.from_email_address.html} {help id="id-from_email" file="CRM/Contact/Form/Task/Help/Email/id-from_email.hlp" title=$form.from_email_address.label} {$form.acknowledge_date.label} @@ -126,7 +126,7 @@ - {include file="CRM/Custom/Form/CustomData.tpl"} + {include file="CRM/common/customDataBlock.tpl" groupID='' customDataType='Pledge' customDataSubType=false entityID=$pledgeID cid=false} {literal} @@ -134,13 +134,13 @@ // bind first click of accordion header to load crm-accordion-body with snippet // everything else taken care of by $().crm-accordions() CRM.$(function($) { - $('.crm-ajax-accordion .crm-accordion-header').one('click', function() { + $('.crm-ajax-accordion summary').one('click', function() { loadPanes($(this).attr('id')); }); $('#currency').on('change', function() { replaceCurrency($('#currency option:selected').text()); }); - $('.crm-ajax-accordion:not(.collapsed) .crm-accordion-header').each(function(index) { + $('.crm-ajax-accordion[open] summary').each(function(index) { loadPanes($(this).attr('id')); }); @@ -173,14 +173,14 @@
{foreach from=$allPanes key=paneName item=paneValue} -
-
+
+ {$paneName} -
+
-
-
+
+ {/foreach}
diff --git a/www/modules/civicrm/templates/CRM/Pledge/Form/PledgeView.tpl b/www/modules/civicrm/templates/CRM/Pledge/Form/PledgeView.tpl index 534808d2e..94f10c560 100644 --- a/www/modules/civicrm/templates/CRM/Pledge/Form/PledgeView.tpl +++ b/www/modules/civicrm/templates/CRM/Pledge/Form/PledgeView.tpl @@ -7,15 +7,10 @@ | and copyright information, see https://civicrm.org/licensing | +--------------------------------------------------------------------+ *} -{math equation="x / y" x=$amount y=$installments format="%.2f" assign="currentInstallment"} -{* Check if current Total Pledge Amount is different from original pledge amount. *} -{if $currentInstallment NEQ $original_installment_amount} - {assign var=originalPledgeAmount value=$installments*$original_installment_amount} -{/if}
- + - + {if $campaign} - + {/if} {if $acknowledge_date} {/if} {if $contribution_page} - - {/if} - {$pledge_status} - {if $honor_contact_id} - + {/if} + {$pledge_status|escape} diff --git a/www/modules/civicrm/templates/CRM/Pledge/Form/Search.tpl b/www/modules/civicrm/templates/CRM/Pledge/Form/Search.tpl index c12a1acb3..0ab3f84b3 100644 --- a/www/modules/civicrm/templates/CRM/Pledge/Form/Search.tpl +++ b/www/modules/civicrm/templates/CRM/Pledge/Form/Search.tpl @@ -9,10 +9,10 @@ *} {* Search form and results for Event Participants *}
-
-
+
+ {ts}Edit Search Criteria{/ts} -
+
@@ -28,7 +28,7 @@ {/strip}
-
+
{if $rowsEmpty || $rows} diff --git a/www/modules/civicrm/templates/CRM/Price/Form/Calculate.tpl b/www/modules/civicrm/templates/CRM/Price/Form/Calculate.tpl index 438f9739e..6bb92cfda 100644 --- a/www/modules/civicrm/templates/CRM/Price/Form/Calculate.tpl +++ b/www/modules/civicrm/templates/CRM/Price/Form/Calculate.tpl @@ -133,7 +133,7 @@ function calculateSelectLineItemValue(priceElement) { function calculateText(priceElement) { //CRM-16034 - comma acts as decimal in price set text pricing //CRM-19937 - dollar sign easy mistake to make by users. - var textval = parseFloat(cj(priceElement).val().replace(thousandMarker, '').replace(symbol, '')); + var textval = parseFloat(cj(priceElement).val().replace(thousandMarker, '').replace(symbol, '').replace(separator, '.')); if (isNaN(textval)) { textval = parseFloat(0); diff --git a/www/modules/civicrm/templates/CRM/Price/Page/LineItem.tpl b/www/modules/civicrm/templates/CRM/Price/Page/LineItem.tpl index e71449121..7663001cd 100644 --- a/www/modules/civicrm/templates/CRM/Price/Page/LineItem.tpl +++ b/www/modules/civicrm/templates/CRM/Price/Page/LineItem.tpl @@ -8,10 +8,7 @@ +--------------------------------------------------------------------+ *} -{* Displays contribution/event fees when price set is used. *} -{if !$currency && $fee_currency} - {assign var=currency value="$fee_currency"} -{/if} +{* Displays contribution/event fees. *} {foreach from=$lineItem item=value key=priceset} {if $value neq 'skip'} @@ -112,7 +109,7 @@ {assign var="lineItemCount" value=0} {foreach from=$pcount item=p_count} - {assign var="intPCount" value=$p_count.participant_count|intval} + {assign var="intPCount" value=$p_count.participant_count|string_format:"%d"} {assign var="lineItemCount" value=$lineItemCount+$intPCount} {/foreach} {if $lineItemCount < 1} @@ -132,22 +129,3 @@ ({$hookDiscount.message}) {/if} -{literal} - -{/literal} diff --git a/www/modules/civicrm/templates/CRM/Profile/Form/Search.tpl b/www/modules/civicrm/templates/CRM/Profile/Form/Search.tpl index a18de33a9..e96797a56 100644 --- a/www/modules/civicrm/templates/CRM/Profile/Form/Search.tpl +++ b/www/modules/civicrm/templates/CRM/Profile/Form/Search.tpl @@ -9,10 +9,10 @@ *} {if ! empty($fields)} {if $groupId} -
-
+
+ {ts}Edit Search Criteria{/ts} -
+
{else}
@@ -49,7 +49,7 @@ {else}
{ts}Pledge By{/ts}{$displayName}
{ts}Pledge By{/ts}{$displayName|escape}
{ts}Total Pledge Amount{/ts} {$amount|crmMoney:$currency} @@ -36,24 +31,21 @@ {/if}
{ts}Financial Type{/ts}{$financial_type} {if $is_test}{ts}(test){/ts}{/if}{$financial_type|escape} {if $is_test}{ts}(test){/ts}{/if}
{ts}Campaign{/ts}{$campaign}{$campaign|escape}
{ts}Received{/ts}{$acknowledge_date|truncate:10:''|crmDate} 
{ts}Self-service Payments Page{/ts}{$contribution_page}
{ts}Pledge Status{/ts}
{$honor_type}{$honor_display}
{ts}Self-service Payments Page{/ts}{$contribution_page|escape}
{ts}Pledge Status{/ts}
{ts}Initial Reminder Day{/ts}{$initial_reminder_day} days prior to schedule date
{ts}Maximum Reminders Send{/ts}{$max_reminders} 
{ts}Send additional reminders{/ts}{$additional_reminder_day} days after the last one sent
- {if $n|substr:0:5 eq 'phone'} + {if $n|str_starts_with:'phone'} {assign var="phone_ext_field" value=$n|replace:'phone':'phone_ext'} {$form.$n.html} {if $form.$phone_ext_field.html} @@ -79,8 +79,12 @@
{if $groupId} -
- + {else} + + {/if} + {if $groupId} + + {/if} {elseif $statusMessage} diff --git a/www/modules/civicrm/templates/CRM/Report/Form/Tabs/FieldSelection.tpl b/www/modules/civicrm/templates/CRM/Report/Form/Tabs/FieldSelection.tpl index 339f51cb5..1db215d52 100644 --- a/www/modules/civicrm/templates/CRM/Report/Form/Tabs/FieldSelection.tpl +++ b/www/modules/civicrm/templates/CRM/Report/Form/Tabs/FieldSelection.tpl @@ -13,10 +13,10 @@ {assign var="count" value=0} {* Wrap custom field sets in collapsed accordion pane. *} {if !empty($grpFields.use_accordian_for_field_selection)} - + + {/if} {/foreach} diff --git a/www/modules/civicrm/templates/CRM/Report/Form/Tabs/Filters.tpl b/www/modules/civicrm/templates/CRM/Report/Form/Tabs/Filters.tpl index b3a701f52..b1b708100 100644 --- a/www/modules/civicrm/templates/CRM/Report/Form/Tabs/Filters.tpl +++ b/www/modules/civicrm/templates/CRM/Report/Form/Tabs/Filters.tpl @@ -14,10 +14,10 @@ {foreach from=$filterGroups item=filterGroup} {* Wrap custom field sets in collapsed accordion pane. *} {if $filterGroup.use_accordion_for_field_selection} - + + {/if} {/foreach} diff --git a/www/modules/civicrm/templates/CRM/Report/Page/InstanceList.tpl b/www/modules/civicrm/templates/CRM/Report/Page/InstanceList.tpl index d12f9e31a..dacb4b4a5 100644 --- a/www/modules/civicrm/templates/CRM/Report/Page/InstanceList.tpl +++ b/www/modules/civicrm/templates/CRM/Report/Page/InstanceList.tpl @@ -20,10 +20,10 @@
{counter start=0 skip=1 print=false} {foreach from=$list item=rows key=report} -
-
+
+ {if $title}{$title}{elseif $report EQ 'Contribute'}{ts}Contribution Reports{/ts}{else}{ts 1=$report}%1 Reports{/ts}{/if} -
+
@@ -37,7 +37,7 @@
    {foreach from=$row.actions item=action key=action_name}
  • {$action.label}
  • {/foreach}
@@ -48,7 +48,7 @@
-
+ {/foreach}
diff --git a/www/modules/civicrm/templates/CRM/Report/Page/TemplateList.tpl b/www/modules/civicrm/templates/CRM/Report/Page/TemplateList.tpl index c19163479..535aca963 100644 --- a/www/modules/civicrm/templates/CRM/Report/Page/TemplateList.tpl +++ b/www/modules/civicrm/templates/CRM/Report/Page/TemplateList.tpl @@ -17,10 +17,10 @@ {if $list} {counter start=0 skip=1 print=false} {foreach from=$list item=rows key=report} -
-
+
+ {if $report}{if $report EQ 'Contribute'}{ts}Contribution{/ts}{else}{$report}{/if}{else}{ts}Contact{/ts}{/if} Report Templates -
+
@@ -41,8 +41,8 @@ {/foreach}
-
-
+ + {/foreach} {else}
diff --git a/www/modules/civicrm/templates/CRM/SMS/Form/Group.tpl b/www/modules/civicrm/templates/CRM/SMS/Form/Group.tpl index 3df24931d..36975c969 100644 --- a/www/modules/civicrm/templates/CRM/SMS/Form/Group.tpl +++ b/www/modules/civicrm/templates/CRM/SMS/Form/Group.tpl @@ -23,10 +23,10 @@
-
-
+
+ {ts}Mailing Recipients{/ts} -
+
{strip} @@ -46,8 +46,8 @@ {/strip} -
-
+
+
{include file="CRM/common/formButtons.tpl" location=''}
diff --git a/www/modules/civicrm/templates/CRM/SMS/Form/Schedule.tpl b/www/modules/civicrm/templates/CRM/SMS/Form/Schedule.tpl index fe5af9ab5..ef564715a 100644 --- a/www/modules/civicrm/templates/CRM/SMS/Form/Schedule.tpl +++ b/www/modules/civicrm/templates/CRM/SMS/Form/Schedule.tpl @@ -27,10 +27,10 @@
{include file="CRM/common/formButtons.tpl" location=''}
{if $preview} - + + {/if} diff --git a/www/modules/civicrm/templates/CRM/SMS/Form/Upload.tpl b/www/modules/civicrm/templates/CRM/SMS/Form/Upload.tpl index c9b425d4c..6b8ac39a2 100644 --- a/www/modules/civicrm/templates/CRM/SMS/Form/Upload.tpl +++ b/www/modules/civicrm/templates/CRM/SMS/Form/Upload.tpl @@ -17,10 +17,12 @@ {include file="CRM/Mailing/Form/Count.tpl"} + {if array_key_exists('SMStemplate', $form)} - + + {/if}
{$form.SMStemplate.label}{$form.SMStemplate.html}{$form.SMStemplate.html}
{$form.upload_type.label} {$form.upload_type.html} {help id="upload-compose"}
diff --git a/www/modules/civicrm/templates/CRM/Tag/Form/Tagtree.tpl b/www/modules/civicrm/templates/CRM/Tag/Form/Tagtree.tpl index 54469f2e3..006d49f33 100644 --- a/www/modules/civicrm/templates/CRM/Tag/Form/Tagtree.tpl +++ b/www/modules/civicrm/templates/CRM/Tag/Form/Tagtree.tpl @@ -13,7 +13,7 @@
  • - {$node.name} + {$node.label} {if $node.children} diff --git a/www/modules/civicrm/templates/CRM/UF/Form/AdvanceSetting.tpl b/www/modules/civicrm/templates/CRM/UF/Form/AdvanceSetting.tpl index 554b3e769..b776100ee 100644 --- a/www/modules/civicrm/templates/CRM/UF/Form/AdvanceSetting.tpl +++ b/www/modules/civicrm/templates/CRM/UF/Form/AdvanceSetting.tpl @@ -7,10 +7,10 @@ | and copyright information, see https://civicrm.org/licensing | +--------------------------------------------------------------------+ *} - + + {literal} {/literal} {/if} +{* jQuery validate *} +{include file="CRM/Form/validate.tpl"} diff --git a/www/modules/civicrm/templates/CRM/common/fatal.tpl b/www/modules/civicrm/templates/CRM/common/fatal.tpl index 7e5a35365..a25c88078 100644 --- a/www/modules/civicrm/templates/CRM/common/fatal.tpl +++ b/www/modules/civicrm/templates/CRM/common/fatal.tpl @@ -13,7 +13,7 @@ - {$pageTitle|escape} + {ts}Error{/ts}