├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── BUG-REPORT.yml │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md ├── SECURITY.md ├── SUPPORT.md ├── stale.yml └── workflows │ └── tests.yml ├── .gitignore ├── Assets ├── css │ └── custom-objects.css ├── img │ └── icon.png └── js │ ├── co-form.js │ └── custom-objects.js ├── Command ├── CustomItemsScheduledExportCommand.php └── GenerateSampleDataCommand.php ├── Config └── config.php ├── Controller ├── CustomField │ ├── FormController.php │ └── SaveController.php ├── CustomItem │ ├── BatchDeleteController.php │ ├── CancelController.php │ ├── ContactListController.php │ ├── DeleteController.php │ ├── ExportController.php │ ├── FormController.php │ ├── LinkController.php │ ├── LinkFormController.php │ ├── ListController.php │ ├── LookupController.php │ ├── SaveController.php │ ├── UnlinkController.php │ └── ViewController.php ├── CustomObject │ ├── CancelController.php │ ├── DeleteController.php │ ├── FormController.php │ ├── ListController.php │ ├── SaveController.php │ └── ViewController.php └── JsonController.php ├── CustomFieldType ├── AbstractCustomFieldType.php ├── AbstractMultivalueType.php ├── AbstractTextType.php ├── CheckboxGroupType.php ├── CountryType.php ├── CustomFieldTypeInterface.php ├── DataTransformer │ ├── CsvTransformer.php │ ├── DateTimeAtomTransformer.php │ ├── DateTimeTransformer.php │ ├── DateTransformer.php │ ├── MultivalueTransformer.php │ └── ViewDateTransformer.php ├── DateOperatorTrait.php ├── DateTimeType.php ├── DateType.php ├── EmailType.php ├── HiddenType.php ├── IntType.php ├── MultiselectType.php ├── PhoneType.php ├── RadioGroupType.php ├── SelectType.php ├── StaticChoiceTypeInterface.php ├── TextType.php ├── TextareaType.php └── UrlType.php ├── CustomItemEvents.php ├── CustomObjectEvents.php ├── CustomObjectsBundle.php ├── DTO ├── CustomItemFieldListData.php ├── ImportLogDTO.php ├── TableConfig.php └── Token.php ├── DataPersister └── CustomItemDataPersister.php ├── DependencyInjection └── Compiler │ └── CustomFieldTypePass.php ├── Entity ├── AbstractCustomFieldValue.php ├── CustomField.php ├── CustomField │ └── Params.php ├── CustomFieldFactory.php ├── CustomFieldOption.php ├── CustomFieldValueDate.php ├── CustomFieldValueDateTime.php ├── CustomFieldValueInt.php ├── CustomFieldValueInterface.php ├── CustomFieldValueOption.php ├── CustomFieldValueText.php ├── CustomItem.php ├── CustomItemExportScheduler.php ├── CustomItemXrefCompany.php ├── CustomItemXrefContact.php ├── CustomItemXrefCustomItem.php ├── CustomItemXrefInterface.php ├── CustomObject.php └── UniqueEntityInterface.php ├── Event ├── CustomItemEvent.php ├── CustomItemExportSchedulerEvent.php ├── CustomItemListDbalQueryEvent.php ├── CustomItemListQueryEvent.php ├── CustomItemXrefEntityDiscoveryEvent.php ├── CustomItemXrefEntityEvent.php ├── CustomObjectEvent.php └── CustomObjectListFormatEvent.php ├── EventListener ├── ApiSubscriber.php ├── AssetsSubscriber.php ├── AuditLogSubscriber.php ├── CampaignSubscriber.php ├── ContactSubscriber.php ├── ContactTabSubscriber.php ├── CustomFieldPostLoadSubscriber.php ├── CustomFieldPreSaveSubscriber.php ├── CustomItemButtonSubscriber.php ├── CustomItemPostDeleteSubscriber.php ├── CustomItemPostSaveSubscriber.php ├── CustomItemScheduledExportSubscriber.php ├── CustomItemTabSubscriber.php ├── CustomItemXrefContactSubscriber.php ├── CustomItemXrefCustomItemSubscriber.php ├── CustomObjectButtonSubscriber.php ├── CustomObjectListFormatSubscriber.php ├── CustomObjectPostSaveSubscriber.php ├── CustomObjectPreDeleteSubscriber.php ├── DynamicContentSubscriber.php ├── FilterOperatorSubscriber.php ├── ImportSubscriber.php ├── MenuSubscriber.php ├── ReportSubscriber.php ├── SegmentFilterDecoratorDelegateSubscriber.php ├── SegmentFiltersChoicesGenerateSubscriber.php ├── SegmentFiltersDictionarySubscriber.php ├── SegmentFiltersMergeSubscriber.php ├── SerializerSubscriber.php └── TokenSubscriber.php ├── Exception ├── ForbiddenException.php ├── InUseException.php ├── InvalidArgumentException.php ├── InvalidCustomObjectFormatListException.php ├── InvalidSegmentFilterException.php ├── InvalidValueException.php ├── NoRelationshipException.php ├── NotFoundException.php └── UndefinedTransformerException.php ├── Extension └── CustomItemListeningExtension.php ├── Form ├── DataTransformer │ ├── CustomObjectHiddenTransformer.php │ ├── OptionsToStringTransformer.php │ ├── OptionsTransformer.php │ └── ParamsToStringTransformer.php ├── Type │ ├── CampaignActionLinkType.php │ ├── CampaignConditionFieldValueType.php │ ├── CustomField │ │ ├── OptionsType.php │ │ └── ParamsType.php │ ├── CustomFieldType.php │ ├── CustomFieldValueType.php │ ├── CustomItemType.php │ └── CustomObjectType.php └── Validator │ └── Constraints │ ├── AllowUniqueIdentifier.php │ ├── AllowUniqueIdentifierValidator.php │ ├── CustomObjectTypeValues.php │ └── CustomObjectTypeValuesValidator.php ├── Helper ├── CsvHelper.php ├── LockFlashMessageHelper.php ├── QueryBuilderManipulatorTrait.php ├── QueryFilterFactory.php ├── QueryFilterFactory │ └── Calculator.php ├── QueryFilterHelper.php ├── RandomHelper.php ├── TokenFormatter.php └── TokenParser.php ├── LICENSE ├── Migrations ├── Version_0_0_1.php ├── Version_0_0_11.php ├── Version_0_0_12.php ├── Version_0_0_13.php ├── Version_0_0_14.php ├── Version_0_0_15.php ├── Version_0_0_16.php ├── Version_0_0_17.php ├── Version_0_0_18.php ├── Version_0_0_19.php ├── Version_0_0_2.php ├── Version_0_0_21.php ├── Version_0_0_22.php ├── Version_0_0_23.php ├── Version_0_0_27.php ├── Version_0_0_3.php ├── Version_0_0_4.php ├── Version_0_0_5.php ├── Version_0_0_6.php ├── Version_0_0_7.php ├── Version_0_0_8.php └── Version_0_0_9.php ├── Model ├── CustomFieldModel.php ├── CustomFieldOptionModel.php ├── CustomFieldValueModel.php ├── CustomItemExportSchedulerModel.php ├── CustomItemImportModel.php ├── CustomItemModel.php ├── CustomItemXrefContactModel.php └── CustomObjectModel.php ├── Provider ├── AbstractPermissionProvider.php ├── ConfigProvider.php ├── CustomFieldPermissionProvider.php ├── CustomFieldRouteProvider.php ├── CustomFieldTypeProvider.php ├── CustomItemPermissionProvider.php ├── CustomItemRouteProvider.php ├── CustomObjectPermissionProvider.php ├── CustomObjectRouteProvider.php ├── SessionProvider.php └── SessionProviderFactory.php ├── README.md ├── Report └── ReportColumnsBuilder.php ├── Repository ├── CustomFieldRepository.php ├── CustomItemExportSchedulerRepository.php ├── CustomItemRepository.php ├── CustomItemXrefContactRepository.php ├── CustomItemXrefCustomItemRepository.php ├── CustomObjectRepository.php └── DbalQueryTrait.php ├── Security └── Permissions │ └── CustomObjectPermissions.php ├── Segment ├── Decorator │ └── MultiselectDecorator.php └── Query │ ├── Filter │ ├── CustomFieldFilterQueryBuilder.php │ ├── CustomItemNameFilterQueryBuilder.php │ ├── CustomObjectMergedFilterQueryBuilder.php │ └── QueryFilterFactory.php │ └── UnionQueryContainer.php ├── Serializer └── ApiNormalizer.php ├── Tests ├── Functional │ ├── ApiPlatform │ │ ├── AbstractApiPlatformFunctionalTest.php │ │ ├── CustomFieldFunctionalTest.php │ │ ├── CustomFieldOptionFunctionalTest.php │ │ ├── CustomItemFunctionalTest.php │ │ └── CustomObjectFunctionalTest.php │ ├── Controller │ │ ├── CustomItemListControllerSearchTest.php │ │ ├── CustomItemListControllerShownFieldTest.php │ │ ├── CustomItemListControllerXrefTest.php │ │ ├── CustomItemLookupControllerTest.php │ │ ├── CustomObject │ │ │ └── DeleteControllerTest.php │ │ └── CustomObjectFormTest.php │ ├── DataFixtures │ │ ├── ORM │ │ │ └── Data │ │ │ │ ├── custom-item-relation-filter-query-builder-fixture-1.yml │ │ │ │ ├── custom-item-relation-filter-query-builder-fixture-2.yml │ │ │ │ ├── custom-item-relation-filter-query-builder-fixture-3.yml │ │ │ │ ├── custom_fields.yml │ │ │ │ ├── custom_items.yml │ │ │ │ ├── custom_objects.yml │ │ │ │ ├── custom_values.yml │ │ │ │ ├── custom_xref.yml │ │ │ │ ├── leads.yml │ │ │ │ ├── roles.yml │ │ │ │ └── users.yml │ │ └── Traits │ │ │ ├── CustomObjectsTrait.php │ │ │ └── FixtureObjectsTrait.php │ ├── EventListener │ │ ├── ApiSubscriberTest.php │ │ ├── CampaignConditionTest.php │ │ ├── CampaignSubscriberTest.php │ │ ├── ContactSubscriberTest.php │ │ ├── DynamicContentSubscriberTest.php │ │ ├── FilterOperatorSubscriberTest.php │ │ ├── ImportSubscriberTest.php │ │ ├── SegmentFiltersDictionarySubscriberTest.php │ │ └── TokenSubscriberTest.php │ ├── Exception │ │ └── FixtureNotFoundException.php │ ├── Helper │ │ └── QueryFilterHelperTest.php │ ├── Segment │ │ └── Query │ │ │ └── Filter │ │ │ ├── CustomFieldFilterQueryBuilderTest.php │ │ │ ├── CustomItemNameFilterQueryBuilderTest.php │ │ │ ├── CustomItemRelationQueryBuilderTest.php │ │ │ ├── CustomObjectMergedFilterQueryBuilderTest.php │ │ │ └── NegativeOperatorFilterQueryBuilderTest.php │ ├── Token │ │ ├── EmailTokenTest.php │ │ └── EmailWithCustomObjectDynamicContentFunctionalTest.php │ └── UpsertFunctionalTest.php ├── ProjectVersionTrait.php └── Unit │ ├── Command │ └── CustomItemExportCommandTest.php │ ├── Controller │ ├── ControllerTestCase.php │ ├── CustomField │ │ ├── AbstractFieldControllerTest.php │ │ ├── FormControllerTest.php │ │ └── SaveControllerTest.php │ ├── CustomItem │ │ ├── BatchDeleteControllerTest.php │ │ ├── CancelControllerTest.php │ │ ├── DeleteControllerTest.php │ │ ├── ExportControllerTest.php │ │ ├── FormControllerTest.php │ │ ├── LinkControllerTest.php │ │ ├── ListControllerTest.php │ │ ├── LookupControllerTest.php │ │ ├── SaveControllerTest.php │ │ ├── UnlinkControllerTest.php │ │ └── ViewControllerTest.php │ └── CustomObject │ │ ├── CancelControllerTest.php │ │ ├── DeleteControllerTest.php │ │ ├── FormControllerTest.php │ │ ├── ListControllerTest.php │ │ ├── SaveControllerTest.php │ │ └── ViewControllerTest.php │ ├── CustomFieldType │ ├── AbstractCustomFieldTypeTest.php │ ├── AbstractMultivalueTypeTest.php │ ├── AbstractTextTypeTest.php │ ├── CountryTypeTest.php │ ├── DataTransformer │ │ ├── CsvTransformerTest.php │ │ ├── DateTimeAtomTransformerTest.php │ │ ├── DateTimeTransformerTest.php │ │ ├── DateTransformerTest.php │ │ ├── MultivalueTransformerTest.php │ │ └── ViewDateTransformerTest.php │ ├── DateTimeTypeTest.php │ ├── DateTypeTest.php │ ├── EmailTypeTest.php │ ├── IntTypeTest.php │ ├── MultiselectTypeTest.php │ ├── PhoneTypeTest.php │ ├── SelectTypeTest.php │ └── UrlTypeTest.php │ ├── CustomObjectTestCase.php │ ├── DTO │ ├── TableConfigTest.php │ └── TokenTest.php │ ├── DependencyInjection │ └── Compiler │ │ └── CustomFieldTypePassTest.php │ ├── Entity │ ├── AbstractCustomFieldValueTest.php │ ├── CustomField │ │ └── ParamsTest.php │ ├── CustomFieldFactoryTest.php │ ├── CustomFieldOptionTest.php │ ├── CustomFieldTest.php │ ├── CustomFieldValueDateTest.php │ ├── CustomFieldValueDateTimeTest.php │ ├── CustomFieldValueOptionTest.php │ ├── CustomItemTest.php │ ├── CustomItemXrefCompanyTest.php │ ├── CustomItemXrefContactTest.php │ ├── CustomItemXrefCustomItemTest.php │ └── CustomObjectTest.php │ ├── Event │ ├── CustomItemEventTest.php │ ├── CustomItemListDbalQueryEventTest.php │ ├── CustomItemListQueryEventTest.php │ ├── CustomItemXrefEntityEventTest.php │ ├── CustomObjectEventTest.php │ └── CustomObjectListFormatEventTest.php │ ├── EventListener │ ├── ApiSubscriberTest.php │ ├── AssetsSubscriberTest.php │ ├── AuditLogSubscriberTest.php │ ├── CampaignSubscriberTest.php │ ├── ContactSubscriberTest.php │ ├── ContactTabSubscriberTest.php │ ├── CustomFieldPostLoadSubscriberTest.php │ ├── CustomItemButtonSubscriberTest.php │ ├── CustomItemPostSaveSubscriberTest.php │ ├── CustomItemTabSubscriberTest.php │ ├── CustomItemXrefContactSubscriberTest.php │ ├── CustomItemXrefCustomItemSubscriberTest.php │ ├── CustomObjectButtonSubscriberTest.php │ ├── CustomObjectListFormatSubscriberTest.php │ ├── CustomObjectPostSaveSubscriberTest.php │ ├── DynamicContentSubscriberTest.php │ ├── FilterOperatorSubscriberTest.php │ ├── ImportSubscriberTest.php │ ├── MenuSubscriberTest.php │ ├── ReportSubscriberTest.php │ ├── SegmentFilterDecoratorDelegateSubscriberTest.php │ ├── SegmentFiltersChoicesGenerateSubscriberTest.php │ ├── SerializerSubscriberTest.php │ └── TokenSubscriberTest.php │ ├── Exception │ ├── InvalidCustomObjectFormatListExceptionTest.php │ └── InvalidValueExceptionTest.php │ ├── Form │ ├── DataTransformer │ │ ├── OptionsToStringTransformerTest.php │ │ ├── OptionsTransformerTest.php │ │ └── ParamsToStringTransformerTest.php │ ├── Type │ │ ├── CampaignConditionFieldValueTypeTest.php │ │ ├── CustomFieldTypeTest.php │ │ ├── CustomFieldValueTypeTest.php │ │ ├── CustomItemTypeTest.php │ │ └── CustomObjectTypeTest.php │ └── Validator │ │ ├── AllowUniqueIdentifierTest.php │ │ ├── AllowUniqueIdentifierValidatorTest.php │ │ ├── CustomObjectTypeValuesTest.php │ │ └── CustomObjectTypeValuesValidatorTest.php │ ├── Helper │ ├── CsvHelperTest.php │ ├── CustomFieldQueryBuilder │ │ └── CalculatorTest.php │ ├── LockFlashMessageHelperTest.php │ ├── QueryFilterFactoryTest.php │ ├── QueryFilterHelperTest.php │ ├── TokenFormatterTest.php │ └── TokenParserTest.php │ ├── Model │ ├── CustomFieldOptionModelTest.php │ ├── CustomFieldValueModelTest.php │ ├── CustomItemImportModelTest.php │ ├── CustomItemModelTest.php │ ├── CustomItemXrefContactModelTest.php │ └── CustomObjectModelTest.php │ ├── Provider │ ├── CustomFieldRouteProviderTest.php │ ├── CustomFieldTypeProviderTest.php │ ├── CustomItemPermissionProviderTest.php │ ├── CustomItemRouteProviderTest.php │ ├── CustomObjectPermissionProviderTest.php │ ├── CustomObjectRouteProviderTest.php │ └── SessionProviderTest.php │ ├── Report │ └── ReportColumnsBuilderTest.php │ ├── Repository │ ├── CustomFieldRepositoryTest.php │ ├── CustomItemRepositoryTest.php │ ├── CustomItemXrefContactRepositoryTest.php │ ├── CustomObjectRepositoryTest.php │ └── DbalQueryTraitTest.php │ ├── Security │ └── Permissions │ │ └── CustomObjectPermissionsTest.php │ ├── Segment │ └── Query │ │ ├── Filter │ │ ├── CustomFieldFilterQueryBuilderTest.php │ │ ├── CustomItemNameFilterQueryBuilderTest.php │ │ └── QueryFilterFactoryTest.php │ │ └── UnionQueryContainerTest.php │ └── Serializer │ └── ApiNormalizerTest.php ├── Translations └── en_US │ ├── flashes.ini │ ├── messages.ini │ └── validators.ini ├── Views ├── CustomField │ ├── detail.html.php │ ├── form.html.php │ ├── index.html.php │ └── value.html.php ├── CustomItem │ ├── detail.html.php │ ├── form.html.php │ ├── index.html.php │ └── list.html.php ├── CustomObject │ ├── Form │ │ └── Panel │ │ │ ├── _field.html.php │ │ │ ├── checkbox_group.html.php │ │ │ ├── country.html.php │ │ │ ├── date.html.php │ │ │ ├── datetime.html.php │ │ │ ├── email.html.php │ │ │ ├── hidden.html.php │ │ │ ├── html_area.html.php │ │ │ ├── int.html.php │ │ │ ├── multiselect.html.php │ │ │ ├── phone.html.php │ │ │ ├── radio_group.html.php │ │ │ ├── select.html.php │ │ │ ├── text.html.php │ │ │ ├── textarea.html.php │ │ │ └── url.html.php │ ├── _form-fields.html.php │ ├── detail.html.php │ ├── form.html.php │ ├── index.html.php │ └── list.html.php ├── FormTheme │ └── FieldValueCondition │ │ └── campaign_condition_field_value_widget.html.php └── SubscribedEvents │ ├── Tab │ ├── content.html.php │ ├── link.html.php │ └── modal.html.php │ └── Timeline │ └── link.html.php ├── composer.json ├── phpstan-baseline-7.4.neon ├── phpstan-baseline-8.0.neon ├── phpstan-baseline-php-versions.neon.php ├── phpstan.neon └── phpunit.xml /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Acquia Custom Objects plugin 2 | Feature requests and bugs are tracked via the [Acquia Custom Objects GitHub issue queue](https://github.com/acquia/mc-cs-plugin-custom-objects/issues). 3 | 4 | Before submitting an issue or pull request, please read and take the time to understand this guide. Issues not adhering to these guidelines may be closed. 5 | 6 | Please be aware that this is **community-supported** software. Contributions in the form of issues or pull requests are welcome, and will be reviewed on a best effort basis. However, Acquia does not provide any direct support for this software or provide any warranty as to its stability. 7 | 8 | ## Submitting issues 9 | 10 | * Issues filed with this project are not subject to an SLA. 11 | * Acquia Custom Objects is distributed under the GPLv3 license; all documentation, code, and guidance is provided without warranty. 12 | * The project maintainers are under no obligation to respond to support requests, feature requests, or pull requests. 13 | 14 | 15 | ## Submitting pull requests 16 | 17 | Pull requests must also adhere to the following guidelines: 18 | - PRs should be atomic and targeted at a single issue rather than broad-scope. 19 | - PRs must contain clear testing steps and justification, as well as all other information required by the pull request template. 20 | - PRs must pass automated tests before they will be reviewed. We recommend you [run the tests locally](https://contribute.mautic.org/contributing-to-mautic/developer/code/pull-requests#automated-testing) before submitting. 21 | - PRs must comply with [Mautic coding standards](https://contribute.mautic.org/contributing-to-mautic/developer/code/pull-requests#code-standards) and best practices as defined by the project maintainers. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Mautic Community Support 4 | url: https://forum.mautic.org/c/support 5 | about: Please ask and answer support questions here. 6 | - name: Mautic Feature Requests 7 | url: https://forum.mautic.org/c/ideas 8 | about: We use GitHub issues as a bug tracker only. Please go to https://forum.mautic.org/c/ideas to find support and discuss features/ideas. -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 14 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 14 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | # Label to use when marking an issue as stale 9 | staleLabel: stale 10 | # Comment to post when marking an issue as stale. Set to `false` to disable 11 | markComment: > 12 | This issue has been automatically marked as stale because it has not had 13 | recent activity. It will be closed if no further activity occurs. Thank you 14 | for your contributions. 15 | # Comment to post when closing a stale issue. Set to `false` to disable 16 | closeComment: true 17 | # Limit the number of actions per hour, from 1-30. Default is 30 18 | limitPerRun: 10 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/* 2 | /Tests/Coverage/* 3 | .idea -------------------------------------------------------------------------------- /Assets/css/custom-objects.css: -------------------------------------------------------------------------------- 1 | .fa.form-control-feedback { 2 | line-height: 32px; 3 | right: 15px; 4 | } 5 | 6 | .panel .btn-group { 7 | z-index: 1000; 8 | } 9 | 10 | input[type=checkbox].form-control { 11 | height: auto; 12 | } 13 | 14 | .drop-here input { 15 | width: 100% !important; 16 | } 17 | 18 | .drop-here input[type="radio"] { 19 | width: initial !important; 20 | } 21 | -------------------------------------------------------------------------------- /Assets/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acquia/mc-cs-plugin-custom-objects/8eedf2d413d711021d93566bc30194c5849d1115/Assets/img/icon.png -------------------------------------------------------------------------------- /Controller/CustomItem/CancelController.php: -------------------------------------------------------------------------------- 1 | sessionProviderFactory = $sessionProviderFactory; 37 | $this->routeProvider = $routeProvider; 38 | $this->customItemModel = $customItemModel; 39 | } 40 | 41 | /** 42 | * @throws NotFoundException 43 | */ 44 | public function cancelAction(int $objectId, ?int $itemId = null): Response 45 | { 46 | $page = $this->sessionProviderFactory->createItemProvider($objectId)->getPage(); 47 | 48 | if ($itemId) { 49 | $customItem = $this->customItemModel->fetchEntity($itemId); 50 | $this->customItemModel->unlockEntity($customItem); 51 | } 52 | 53 | return $this->postActionRedirect( 54 | [ 55 | 'returnUrl' => $this->routeProvider->buildListRoute($objectId, $page), 56 | 'viewParameters' => ['objectId' => $objectId, 'page' => $page], 57 | 'contentTemplate' => 'CustomObjectsBundle:CustomItem\List:list', 58 | 'passthroughVars' => [ 59 | 'mauticContent' => 'customItem', 60 | ], 61 | ] 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Controller/CustomItem/ContactListController.php: -------------------------------------------------------------------------------- 1 | generateContactsGrid( 21 | $objectId, 22 | $page, 23 | 'lead:lists:viewother', 24 | 'custom_item', 25 | 'custom_item_xref_contact', 26 | null, 27 | 'custom_item_id', 28 | [], 29 | [], 30 | 'contact_id' 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Controller/CustomObject/CancelController.php: -------------------------------------------------------------------------------- 1 | sessionProviderFactory = $sessionProviderFactory; 36 | $this->routeProvider = $routeProvider; 37 | $this->customObjectModel = $customObjectModel; 38 | } 39 | 40 | public function cancelAction(?int $objectId): Response 41 | { 42 | $page = $this->sessionProviderFactory->createObjectProvider()->getPage(); 43 | 44 | if ($objectId) { 45 | $customObject = $this->customObjectModel->fetchEntity($objectId); 46 | $this->customObjectModel->unlockEntity($customObject); 47 | } 48 | 49 | return $this->postActionRedirect( 50 | [ 51 | 'returnUrl' => $this->routeProvider->buildListRoute($page), 52 | 'viewParameters' => ['page' => $page], 53 | 'contentTemplate' => 'CustomObjectsBundle:CustomObject\List:list', 54 | 'passthroughVars' => [ 55 | 'mauticContent' => 'customObject', 56 | ], 57 | ] 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Controller/JsonController.php: -------------------------------------------------------------------------------- 1 | renderView('MauticCoreBundle:Notification:flash_messages.html.php'); 20 | 21 | return new JsonResponse($responseData); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /CustomFieldType/AbstractTextType.php: -------------------------------------------------------------------------------- 1 | true, 24 | 'multiple' => true, 25 | ]; 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function usePlaceholder(): bool 31 | { 32 | return false; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /CustomFieldType/CountryType.php: -------------------------------------------------------------------------------- 1 | countryList) { 34 | $this->countryList = array_flip(FormFieldHelper::getCountryChoices()); 35 | } 36 | 37 | return $this->countryList; 38 | } 39 | 40 | public function getSymfonyFormFieldType(): string 41 | { 42 | return SymfonyCountryType::class; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function configureOptions(OptionsResolver $resolver): void 49 | { 50 | $resolver->setDefaults([ 51 | 'choices' => ['choices' => $this->getChoices()], 52 | 'choice_translation_domain' => false, 53 | ]); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /CustomFieldType/DataTransformer/CsvTransformer.php: -------------------------------------------------------------------------------- 1 | csvHelper = new CsvHelper(); 20 | } 21 | 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public function transform($value) 26 | { 27 | if (empty($value)) { 28 | return ''; 29 | } 30 | 31 | if (is_array($value)) { 32 | return $this->csvHelper->arrayToCsvLine($value); 33 | } 34 | 35 | return $value; 36 | } 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | public function reverseTransform($value) 42 | { 43 | if (empty($value)) { 44 | return []; 45 | } 46 | 47 | if (is_string($value)) { 48 | return $this->csvHelper->csvLineToArray($value); 49 | } 50 | 51 | return $value; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /CustomFieldType/DataTransformer/DateTimeAtomTransformer.php: -------------------------------------------------------------------------------- 1 | format(DATE_ATOM); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /CustomFieldType/DataTransformer/DateTimeTransformer.php: -------------------------------------------------------------------------------- 1 | format('Y-m-d H:i'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /CustomFieldType/DataTransformer/DateTransformer.php: -------------------------------------------------------------------------------- 1 | format('Y-m-d'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /CustomFieldType/DataTransformer/MultivalueTransformer.php: -------------------------------------------------------------------------------- 1 | translator->trans('custom.field.email.invalid', ['%value%' => $value], 'validators')); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /CustomFieldType/HiddenType.php: -------------------------------------------------------------------------------- 1 | false, 24 | 'multiple' => true, 25 | ]; 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function usePlaceholder(): bool 31 | { 32 | return true; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /CustomFieldType/PhoneType.php: -------------------------------------------------------------------------------- 1 | translator->trans('custom.field.phone.invalid', ['%value%' => $value], 'validators'); 36 | 37 | try { 38 | $phoneNumber = $phoneUtil->parse($value, PhoneNumberUtil::UNKNOWN_REGION); 39 | } catch (NumberParseException $e) { 40 | throw new \UnexpectedValueException($message); 41 | } 42 | 43 | if (false === $phoneUtil->isValidNumber($phoneNumber)) { 44 | throw new \UnexpectedValueException($message); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /CustomFieldType/RadioGroupType.php: -------------------------------------------------------------------------------- 1 | true, 24 | 'multiple' => false, 25 | ]; 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function usePlaceholder(): bool 31 | { 32 | return false; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /CustomFieldType/SelectType.php: -------------------------------------------------------------------------------- 1 | false, 28 | 'multiple' => false, 29 | ]; 30 | 31 | public function getSymfonyFormFieldType(): string 32 | { 33 | return ChoiceType::class; 34 | } 35 | 36 | /** 37 | * @return mixed[] 38 | */ 39 | public function getOperators(): array 40 | { 41 | $allOperators = parent::getOperators(); 42 | $allowedOperators = array_flip(['=', '!=', 'empty', '!empty']); 43 | 44 | return array_intersect_key($allOperators, $allowedOperators); 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function valueToString(CustomFieldValueInterface $fieldValue): string 51 | { 52 | $value = $fieldValue->getValue(); 53 | 54 | try { 55 | return $fieldValue->getCustomField()->valueToLabel((string) $value); 56 | } catch (NotFoundException $e) { 57 | // When the value does not exist anymore, use the value instead. 58 | return $value; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /CustomFieldType/StaticChoiceTypeInterface.php: -------------------------------------------------------------------------------- 1 | null]; 24 | 25 | /** 26 | * @var string 27 | */ 28 | protected $key = 'url'; 29 | 30 | public function getSymfonyFormFieldType(): string 31 | { 32 | return \Symfony\Component\Form\Extension\Core\Type\UrlType::class; 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function validateValue(CustomField $customField, $value): void 39 | { 40 | parent::validateValue($customField, $value); 41 | 42 | if (empty($value)) { 43 | return; 44 | } 45 | 46 | $constraint = new \Symfony\Component\Validator\Constraints\Url(); 47 | $pattern = sprintf(UrlValidator::PATTERN, implode('|', $constraint->protocols)); 48 | 49 | if (!preg_match($pattern, $value)) { 50 | throw new \UnexpectedValueException($this->translator->trans('custom.field.url.invalid', ['%value%' => $value], 'validators')); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /CustomObjectsBundle.php: -------------------------------------------------------------------------------- 1 | addCompilerPass(new CustomFieldTypePass()); 21 | } 22 | 23 | /** 24 | * In some rare cases it can happen that the plugin tables weren't created on plugin install. 25 | * Create them on plugin update if they are missing. 26 | */ 27 | protected static function installAllTablesIfMissing(Schema $schema, string $tablePrefix, MauticFactory $factory, array $metadata = null): void 28 | { 29 | if (!$schema->hasTable($tablePrefix.'custom_object')) { 30 | self::installPluginSchema($metadata, $factory, null); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /DTO/CustomItemFieldListData.php: -------------------------------------------------------------------------------- 1 | columns = $columns; 24 | $this->data = $data; 25 | } 26 | 27 | public function getColumnLabels(): array 28 | { 29 | return $this->columns; 30 | } 31 | 32 | /** 33 | * @return CustomFieldValueInterface[] 34 | */ 35 | public function getFields(int $itemId): array 36 | { 37 | return $this->data[$itemId]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /DTO/ImportLogDTO.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | private array $warnings = []; 13 | 14 | public function hasWarning(): bool 15 | { 16 | return !empty($this->warnings); 17 | } 18 | 19 | public function addWarning(string $warning): void 20 | { 21 | $this->warnings[] = $warning; 22 | } 23 | 24 | public function getWarningsAsString(): string 25 | { 26 | return implode('\n', $this->warnings); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /DataPersister/CustomItemDataPersister.php: -------------------------------------------------------------------------------- 1 | customItemModel = $customItemModel; 16 | } 17 | 18 | /** 19 | * @param mixed $data 20 | */ 21 | public function supports($data): bool 22 | { 23 | return $data instanceof CustomItem; 24 | } 25 | 26 | /** 27 | * @param mixed $data 28 | * 29 | * @return mixed 30 | */ 31 | public function persist($data) 32 | { 33 | \assert($data instanceof CustomItem); 34 | 35 | $this->customItemModel->save($data); 36 | 37 | return $data; 38 | } 39 | 40 | /** 41 | * @param mixed $data 42 | */ 43 | public function remove($data): void 44 | { 45 | \assert($data instanceof CustomItem); 46 | 47 | $this->customItemModel->delete($data); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/CustomFieldTypePass.php: -------------------------------------------------------------------------------- 1 | findDefinition('custom_field.type.provider'); 20 | $customFieldTypeDiKeys = array_keys($container->findTaggedServiceIds('custom.field.type')); 21 | 22 | foreach ($customFieldTypeDiKeys as $id) { 23 | $customFieldType = $container->findDefinition($id); 24 | $customFieldTypeProvider->addMethodCall('addType', [$customFieldType]); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Entity/CustomField/Params.php: -------------------------------------------------------------------------------- 1 | $value) { 23 | $this->{$key} = $value; 24 | } 25 | } 26 | 27 | /** 28 | * Used as data source for json serialization. 29 | * 30 | * @return mixed[] 31 | */ 32 | public function __toArray(): array 33 | { 34 | $return = [ 35 | 'placeholder' => $this->placeholder, 36 | ]; 37 | 38 | // Remove null and false values as they are default 39 | return array_filter($return); 40 | } 41 | 42 | public function getPlaceholder(): ?string 43 | { 44 | return $this->placeholder; 45 | } 46 | 47 | public function setPlaceholder(?string $placeholder): void 48 | { 49 | $this->placeholder = $placeholder; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Entity/CustomFieldFactory.php: -------------------------------------------------------------------------------- 1 | customFieldTypeProvider = $customFieldTypeProvider; 21 | } 22 | 23 | /** 24 | * @throws NotFoundException 25 | */ 26 | public function create(string $type, CustomObject $customObject): CustomField 27 | { 28 | $typeObject = $this->customFieldTypeProvider->getType($type); 29 | 30 | $customField = new CustomField(); 31 | 32 | $customField->setType($type); 33 | $customField->setTypeObject($typeObject); 34 | $customField->setCustomObject($customObject); 35 | $customField->setParams(new Params()); 36 | 37 | return $customField; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Entity/CustomFieldValueDate.php: -------------------------------------------------------------------------------- 1 | setValue($value); 24 | } 25 | 26 | public static function loadMetadata(ORM\ClassMetadata $metadata): void 27 | { 28 | $builder = new ClassMetadataBuilder($metadata); 29 | $builder->setTable('custom_field_value_date'); 30 | $builder->addIndex(['value'], 'value_index'); 31 | $builder->addNullableField('value', Type::DATE); 32 | 33 | parent::addReferenceColumns($builder); 34 | } 35 | 36 | /** 37 | * @param mixed $value 38 | */ 39 | public function setValue($value = null): void 40 | { 41 | if (empty($value)) { 42 | $this->value = null; 43 | 44 | return; 45 | } 46 | 47 | if (!$value instanceof DateTimeInterface) { 48 | $value = new \DateTimeImmutable($value); 49 | } 50 | 51 | $this->value = $value; 52 | } 53 | 54 | /** 55 | * @return mixed 56 | */ 57 | public function getValue() 58 | { 59 | return $this->value; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Entity/CustomFieldValueDateTime.php: -------------------------------------------------------------------------------- 1 | value = $value; 24 | } 25 | 26 | public static function loadMetadata(ORM\ClassMetadata $metadata): void 27 | { 28 | $builder = new ClassMetadataBuilder($metadata); 29 | $builder->setTable('custom_field_value_datetime'); 30 | $builder->addIndex(['value'], 'value_index'); 31 | $builder->addNullableField('value', Type::DATETIME); 32 | 33 | parent::addReferenceColumns($builder); 34 | } 35 | 36 | /** 37 | * @param mixed $value 38 | */ 39 | public function setValue($value = null): void 40 | { 41 | if (empty($value)) { 42 | $this->value = null; 43 | 44 | return; 45 | } 46 | 47 | if (!$value instanceof DateTimeInterface) { 48 | $value = new \DateTimeImmutable($value); 49 | } 50 | 51 | $this->value = $value; 52 | } 53 | 54 | /** 55 | * @return mixed 56 | */ 57 | public function getValue() 58 | { 59 | return $this->value; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Entity/CustomFieldValueInt.php: -------------------------------------------------------------------------------- 1 | value = $value; 23 | } 24 | 25 | public static function loadMetadata(ORM\ClassMetadata $metadata): void 26 | { 27 | $builder = new ClassMetadataBuilder($metadata); 28 | $builder->setTable('custom_field_value_int'); 29 | $builder->addIndex(['value'], 'value_index'); 30 | $builder->addNullableField('value', Type::INTEGER); 31 | 32 | parent::addReferenceColumns($builder); 33 | } 34 | 35 | /** 36 | * @param mixed $value 37 | */ 38 | public function setValue($value = null): void 39 | { 40 | $this->value = null === $value ? null : (int) $value; 41 | } 42 | 43 | /** 44 | * @return mixed 45 | */ 46 | public function getValue() 47 | { 48 | return $this->value; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Entity/CustomFieldValueInterface.php: -------------------------------------------------------------------------------- 1 | value = $value; 23 | } 24 | 25 | /** 26 | * Doctrine doesn't support prefix indexes. It's being added in the updatePluginSchema method. 27 | * $builder->addIndex(['value(64)'], 'value_index');. 28 | */ 29 | public static function loadMetadata(ORM\ClassMetadata $metadata): void 30 | { 31 | $builder = new ClassMetadataBuilder($metadata); 32 | $builder->setTable('custom_field_value_text') 33 | ->addNullableField('value', Type::TEXT) 34 | ->addFulltextIndex(['value'], 'value_fulltext'); 35 | 36 | parent::addReferenceColumns($builder); 37 | } 38 | 39 | /** 40 | * @param mixed $value 41 | */ 42 | public function setValue($value = null): void 43 | { 44 | $this->value = (string) $value; 45 | } 46 | 47 | /** 48 | * @return mixed 49 | */ 50 | public function getValue() 51 | { 52 | return $this->value; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Entity/CustomItemXrefInterface.php: -------------------------------------------------------------------------------- 1 | customItem = $customItem; 25 | $this->isNew = $isNew; 26 | } 27 | 28 | public function getCustomItem(): CustomItem 29 | { 30 | return $this->customItem; 31 | } 32 | 33 | public function entityIsNew(): bool 34 | { 35 | return $this->isNew; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Event/CustomItemExportSchedulerEvent.php: -------------------------------------------------------------------------------- 1 | customItemExportScheduler = $customItemExportScheduler; 19 | } 20 | 21 | public function getCustomItemExportScheduler(): CustomItemExportScheduler 22 | { 23 | return $this->customItemExportScheduler; 24 | } 25 | 26 | public function getFilePath(): string 27 | { 28 | return $this->filePath; 29 | } 30 | 31 | public function setFilePath(string $filePath): void 32 | { 33 | $this->filePath = $filePath; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Event/CustomItemListDbalQueryEvent.php: -------------------------------------------------------------------------------- 1 | queryBuilder = $queryBuilder; 26 | $this->tableConfig = $tableConfig; 27 | } 28 | 29 | public function getQueryBuilder(): QueryBuilder 30 | { 31 | return $this->queryBuilder; 32 | } 33 | 34 | public function getTableConfig(): TableConfig 35 | { 36 | return $this->tableConfig; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Event/CustomItemListQueryEvent.php: -------------------------------------------------------------------------------- 1 | queryBuilder = $queryBuilder; 26 | $this->tableConfig = $tableConfig; 27 | } 28 | 29 | public function getQueryBuilder(): QueryBuilder 30 | { 31 | return $this->queryBuilder; 32 | } 33 | 34 | public function getTableConfig(): TableConfig 35 | { 36 | return $this->tableConfig; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Event/CustomItemXrefEntityDiscoveryEvent.php: -------------------------------------------------------------------------------- 1 | customItem = $customItem; 36 | $this->entityType = $entityType; 37 | $this->entityId = $entityId; 38 | } 39 | 40 | public function getCustomItem(): CustomItem 41 | { 42 | return $this->customItem; 43 | } 44 | 45 | public function getEntityType(): string 46 | { 47 | return $this->entityType; 48 | } 49 | 50 | public function getEntityId(): int 51 | { 52 | return $this->entityId; 53 | } 54 | 55 | public function setXrefEntity(CustomItemXrefInterface $customItemXrefEntity): void 56 | { 57 | $this->customItemXrefEntity = $customItemXrefEntity; 58 | } 59 | 60 | /** 61 | * @return ?CustomItemXrefInterface 62 | */ 63 | public function getXrefEntity(): ?CustomItemXrefInterface 64 | { 65 | return $this->customItemXrefEntity; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Event/CustomItemXrefEntityEvent.php: -------------------------------------------------------------------------------- 1 | xRef = $xRef; 20 | } 21 | 22 | public function getXref(): CustomItemXrefInterface 23 | { 24 | return $this->xRef; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Event/CustomObjectEvent.php: -------------------------------------------------------------------------------- 1 | customObject = $customObject; 36 | $this->isNew = $isNew; 37 | } 38 | 39 | public function getCustomObject(): CustomObject 40 | { 41 | return $this->customObject; 42 | } 43 | 44 | public function entityIsNew(): bool 45 | { 46 | return $this->isNew; 47 | } 48 | 49 | public function getMessage(): string 50 | { 51 | return (string) $this->message; 52 | } 53 | 54 | public function setMessage(string $message): void 55 | { 56 | $this->message = $message; 57 | } 58 | 59 | public function getFlashBag(): ?FlashBag 60 | { 61 | return $this->flashBag; 62 | } 63 | 64 | public function setFlashBag(FlashBag $flashBag): void 65 | { 66 | $this->flashBag = $flashBag; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Event/CustomObjectListFormatEvent.php: -------------------------------------------------------------------------------- 1 | customObjectValues = $customObjectValues; 34 | $this->format = $format; 35 | } 36 | 37 | public function getCustomObjectValues(): array 38 | { 39 | return $this->customObjectValues; 40 | } 41 | 42 | public function getFormat(): string 43 | { 44 | return $this->format; 45 | } 46 | 47 | public function getFormattedString(): string 48 | { 49 | return $this->formattedString; 50 | } 51 | 52 | public function setFormattedString(string $formattedString): void 53 | { 54 | if ('' !== $formattedString) { 55 | $this->hasBeenFormatted = true; 56 | $this->formattedString = $formattedString; 57 | } 58 | } 59 | 60 | public function hasBeenFormatted(): bool 61 | { 62 | return $this->hasBeenFormatted; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /EventListener/AssetsSubscriber.php: -------------------------------------------------------------------------------- 1 | assetHelper = $assetHelper; 31 | $this->configProvider = $configProvider; 32 | } 33 | 34 | /** 35 | * @return mixed[] 36 | */ 37 | public static function getSubscribedEvents(): array 38 | { 39 | return [ 40 | KernelEvents::REQUEST => ['loadAssets', -255], 41 | ]; 42 | } 43 | 44 | public function loadAssets(GetResponseEvent $event): void 45 | { 46 | if ($this->configProvider->pluginIsEnabled() && $event->isMasterRequest() && $this->isMauticAdministrationPage($event->getRequest())) { 47 | $this->assetHelper->addScript('plugins/CustomObjectsBundle/Assets/js/custom-objects.js'); 48 | $this->assetHelper->addScript('plugins/CustomObjectsBundle/Assets/js/co-form.js'); 49 | $this->assetHelper->addStylesheet('plugins/CustomObjectsBundle/Assets/css/custom-objects.css'); 50 | } 51 | } 52 | 53 | /** 54 | * Returns true for routes that starts with /s/. 55 | */ 56 | private function isMauticAdministrationPage(Request $request): bool 57 | { 58 | return preg_match('/^\/s\//', $request->getPathInfo()) >= 1; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /EventListener/CustomFieldPostLoadSubscriber.php: -------------------------------------------------------------------------------- 1 | customFieldTypeProvider = $customFieldTypeProvider; 27 | } 28 | 29 | /** 30 | * @return mixed[] 31 | */ 32 | public function getSubscribedEvents(): array 33 | { 34 | return [ 35 | Events::postLoad, 36 | ]; 37 | } 38 | 39 | /** 40 | * @throws NotFoundException 41 | */ 42 | public function postLoad(LifecycleEventArgs $args): void 43 | { 44 | $customField = $args->getObject(); 45 | 46 | if (!$customField instanceof CustomField) { 47 | return; 48 | } 49 | 50 | $type = $customField->getType(); 51 | 52 | $customField->setTypeObject($this->customFieldTypeProvider->getType($type)); 53 | 54 | if (is_array($customField->getParams())) { 55 | $customField->setParams(new CustomField\Params($customField->getParams())); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /EventListener/CustomFieldPreSaveSubscriber.php: -------------------------------------------------------------------------------- 1 | customFieldOptionModel = $customFieldOptionModel; 30 | } 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public static function getSubscribedEvents() 36 | { 37 | // Doctrine event preFlush could not be used, 38 | // because it does not contain entity itself 39 | return [ 40 | CustomObjectEvents::ON_CUSTOM_OBJECT_PRE_SAVE => 'preSave', 41 | ]; 42 | } 43 | 44 | public function preSave(CustomObjectEvent $event): void 45 | { 46 | $event->getCustomObject()->getCustomFields()->map( 47 | function (CustomField $customField): void { 48 | if ($customField->getId()) { 49 | $this->customFieldOptionModel->deleteByCustomFieldId($customField->getId()); 50 | } 51 | 52 | $params = $customField->getParams(); 53 | 54 | if ($params instanceof Params) { 55 | $customField->setParams($params->__toArray()); 56 | } else { 57 | $customField->setParams($params); 58 | } 59 | } 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /EventListener/CustomItemPostDeleteSubscriber.php: -------------------------------------------------------------------------------- 1 | customItemXrefCustomItemRepository = $customItemXrefCustomItemRepository; 30 | $this->customItemXrefContactRepository = $customItemXrefContactRepository; 31 | } 32 | 33 | public static function getSubscribedEvents() 34 | { 35 | return [ 36 | CustomItemEvents::ON_CUSTOM_ITEM_POST_DELETE => 'onPostDelete', 37 | ]; 38 | } 39 | 40 | /** 41 | * Links the master object item with the entityType after a relationship object is created. 42 | */ 43 | public function onPostDelete(CustomItemEvent $event): void 44 | { 45 | $this->customItemXrefCustomItemRepository->deleteAllLinksForCustomItem($event->getCustomItem()->deletedId); 46 | $this->customItemXrefContactRepository->deleteAllLinksForCustomItem($event->getCustomItem()->deletedId); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /EventListener/CustomItemScheduledExportSubscriber.php: -------------------------------------------------------------------------------- 1 | customItemExportSchedulerModel = $customItemExportSchedulerModel; 19 | } 20 | 21 | /** 22 | * @return array 23 | */ 24 | public static function getSubscribedEvents(): array 25 | { 26 | return [ 27 | CustomItemEvents::CUSTOM_ITEM_PREPARE_EXPORT_FILE => 'onCustomItemExportScheduled', 28 | CustomItemEvents::CUSTOM_ITEM_MAIL_EXPORT_FILE => 'sendEmail', 29 | CustomItemEvents::POST_EXPORT_MAIL_SENT => 'onExportEmailSent', 30 | ]; 31 | } 32 | 33 | public function onCustomItemExportScheduled(CustomItemExportSchedulerEvent $event): void 34 | { 35 | $customItemExportScheduler = $event->getCustomItemExportScheduler(); 36 | $filePath = $this->customItemExportSchedulerModel->processDataAndGetExportFilePath($customItemExportScheduler); 37 | $event->setFilePath($filePath); 38 | } 39 | 40 | public function sendEmail(CustomItemExportSchedulerEvent $event): void 41 | { 42 | $customItemExportScheduler = $event->getCustomItemExportScheduler(); 43 | $this->customItemExportSchedulerModel->sendEmail($customItemExportScheduler, $event->getFilePath()); 44 | } 45 | 46 | public function onExportEmailSent(CustomItemExportSchedulerEvent $event): void 47 | { 48 | $customItemExportScheduler = $event->getCustomItemExportScheduler(); 49 | $this->customItemExportSchedulerModel->deleteEntity($customItemExportScheduler); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /EventListener/CustomObjectListFormatSubscriber.php: -------------------------------------------------------------------------------- 1 | tokenFormatter = $tokenFormatter; 22 | } 23 | 24 | public static function getSubscribedEvents(): array 25 | { 26 | return [ 27 | CustomObjectEvents::ON_CUSTOM_OBJECT_LIST_FORMAT => ['onFormatList', 0], 28 | ]; 29 | } 30 | 31 | public function onFormatList(CustomObjectListFormatEvent $event): void 32 | { 33 | $format = $event->getFormat(); 34 | if ($this->tokenFormatter->isValidFormat($format)) { 35 | $values = $event->getCustomObjectValues(); 36 | $event->setFormattedString($this->tokenFormatter->format($values, $format)); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /EventListener/CustomObjectPostSaveSubscriber.php: -------------------------------------------------------------------------------- 1 | customObjectModel = $customObjectModel; 23 | } 24 | 25 | public static function getSubscribedEvents() 26 | { 27 | return [ 28 | CustomObjectEvents::ON_CUSTOM_OBJECT_POST_SAVE => 'postSave', 29 | ]; 30 | } 31 | 32 | /** 33 | * Persists the relationship object to the. 34 | */ 35 | public function postSave(CustomObjectEvent $event): void 36 | { 37 | $object = $event->getCustomObject(); 38 | 39 | if (CustomObject::TYPE_MASTER === $object->getType()) { 40 | return; 41 | } 42 | 43 | $object->getMasterObject()->setRelationshipObject($object); 44 | 45 | $this->customObjectModel->saveEntity($object->getMasterObject()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /EventListener/CustomObjectPreDeleteSubscriber.php: -------------------------------------------------------------------------------- 1 | customObjectModel = $customObjectModel; 28 | $this->translator = $translator; 29 | } 30 | 31 | public static function getSubscribedEvents() 32 | { 33 | return [ 34 | CustomObjectEvents::ON_CUSTOM_OBJECT_USER_PRE_DELETE => 'preDelete', 35 | ]; 36 | } 37 | 38 | public function preDelete(CustomObjectEvent $event): void 39 | { 40 | $customObject = $event->getCustomObject(); 41 | $this->customObjectModel->delete($customObject); 42 | 43 | $flashBag = $event->getFlashBag(); 44 | if (!$flashBag) { 45 | return; 46 | } 47 | 48 | $message = $this->translator->trans('mautic.core.notice.deleted', ['%name%' => $customObject->getName()], 'flashes'); 49 | $flashBag->add($message); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /EventListener/SegmentFilterDecoratorDelegateSubscriber.php: -------------------------------------------------------------------------------- 1 | multiselectDecorator = $multiselectDecorator; 22 | } 23 | 24 | /** 25 | * @return mixed[] 26 | */ 27 | public static function getSubscribedEvents(): array 28 | { 29 | return [ 30 | LeadEvents::SEGMENT_ON_DECORATOR_DELEGATE => 'onDecoratorDelegate', 31 | ]; 32 | } 33 | 34 | public function onDecoratorDelegate(LeadListFiltersDecoratorDelegateEvent $delegateEvent): void 35 | { 36 | $crate = $delegateEvent->getCrate(); 37 | 38 | if ('custom_object' === $crate->getObject()) { 39 | switch ($crate->getType()) { 40 | case 'multiselect': 41 | $delegateEvent->setDecorator($this->multiselectDecorator); 42 | $delegateEvent->stopPropagation(); 43 | 44 | break; 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Exception/ForbiddenException.php: -------------------------------------------------------------------------------- 1 | segmentList = $segmentList; 19 | } 20 | 21 | public function getSegmentList(): array 22 | { 23 | return $this->segmentList; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | customField = $customField; 20 | } 21 | 22 | public function getCustomField(): ?CustomField 23 | { 24 | return $this->customField; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Exception/NoRelationshipException.php: -------------------------------------------------------------------------------- 1 | customObjectRepository = $customObjectRepository; 22 | } 23 | 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function transform($value) 28 | { 29 | if (!$value) { 30 | return ''; 31 | } 32 | 33 | return $value->getId(); 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function reverseTransform($value) 40 | { 41 | if (!$value) { 42 | return new CustomObject(); 43 | } 44 | 45 | $entity = $this->customObjectRepository->findOneBy(['id' => $value]); 46 | 47 | if (null === $entity) { 48 | throw new TransformationFailedException(sprintf('An entity with ID "%s" does not exist!', $value)); 49 | } 50 | 51 | return $entity; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Form/DataTransformer/OptionsTransformer.php: -------------------------------------------------------------------------------- 1 | count()) { 22 | return ['list' => []]; 23 | } 24 | 25 | $optionList = []; 26 | 27 | foreach ($value as $option) { 28 | $optionList[] = $option; 29 | } 30 | 31 | return [ 32 | 'list' => $optionList, 33 | ]; 34 | } 35 | 36 | /** 37 | * @param string[] $value 38 | */ 39 | public function reverseTransform($value): ArrayCollection 40 | { 41 | $values = []; 42 | $options = []; 43 | 44 | /** @var CustomFieldOption|array $option */ 45 | foreach ($value['list'] as $key => $option) { 46 | if (is_array($option)) { 47 | // Newly created option 48 | $option = new CustomFieldOption($option); 49 | $option->setOrder($key); 50 | } 51 | 52 | if (!$option->getLabel() || !$option->getValue()) { 53 | // Remove incomplete options (missing label or value) represented as array, not CustomFieldOption 54 | unset($value['list'][$key]); 55 | 56 | continue; 57 | } 58 | 59 | if (in_array($option->getValue(), $values, false)) { 60 | // Remove options with the same value as invalid 61 | unset($value['list'][$key]); 62 | 63 | continue; 64 | } 65 | 66 | $values[] = $option->getValue(); 67 | $options[] = $option; 68 | } 69 | 70 | return new ArrayCollection($options); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Form/DataTransformer/ParamsToStringTransformer.php: -------------------------------------------------------------------------------- 1 | serializer = $serializer; 21 | } 22 | 23 | /** 24 | * Transforms an object (Params) to a string (json). 25 | * 26 | * @param Params|null $params 27 | */ 28 | public function transform($params = null): string 29 | { 30 | if (null === $params) { 31 | // Param can be null because entities are not using constructors 32 | return '[]'; 33 | } 34 | 35 | if ($params instanceof Params) { 36 | $params = $params->__toArray(); 37 | } 38 | 39 | return $this->serializer->serialize($params, 'json'); 40 | } 41 | 42 | /** 43 | * Transforms a string (json) to an object (Params). 44 | * 45 | * @param string $params 46 | */ 47 | public function reverseTransform($params): Params 48 | { 49 | $params = json_decode($params, true); 50 | 51 | return new Params($params); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Form/Type/CustomField/ParamsType.php: -------------------------------------------------------------------------------- 1 | add( 22 | 'placeholder', 23 | TextType::class, 24 | [ 25 | 'label' => 'custom.field.label.placeholder', 26 | 'required' => false, 27 | 'attr' => [ 28 | 'class' => 'form-control', 29 | 'tooltip' => 'custom.field.help.placeholder', 30 | ], 31 | ] 32 | ); 33 | } 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function configureOptions(OptionsResolver $resolver): void 40 | { 41 | $resolver->setDefaults( 42 | [ 43 | 'label' => false, 44 | 'data_class' => Params::class, 45 | 'custom_object_form' => false, 46 | 'csrf_protection' => false, 47 | 'has_choices' => false, 48 | 'use_placeholder' => false, // @see \MauticPlugin\CustomObjectsBundle\CustomFieldType\AbstractCustomFieldType::usePlaceholder() 49 | ] 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Form/Type/CustomFieldValueType.php: -------------------------------------------------------------------------------- 1 | getName(); // @todo Check Symfony 3 compatibility 27 | $customFieldValue = $customItem->findCustomFieldValueForFieldId($customFieldId); 28 | $customField = $customFieldValue->getCustomField(); 29 | $symfonyFormType = $customField->getTypeObject()->getSymfonyFormFieldType(); 30 | $options = $customItem->getId() ? [] : ['data' => $customField->getDefaultValue()]; 31 | $options = $customField->getFormFieldOptions($options); 32 | $formField = $builder->create('value', $symfonyFormType, $options); 33 | 34 | try { 35 | $viewTransformer = $customField->getTypeObject()->createViewTransformer(); 36 | $formField->addViewTransformer($viewTransformer); 37 | } catch (UndefinedTransformerException $e) { 38 | } 39 | 40 | $builder->add($formField); 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function configureOptions(OptionsResolver $resolver): void 47 | { 48 | $resolver->setDefaults(['data_class' => CustomFieldValueInterface::class]); 49 | $resolver->setRequired(['customItem']); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Form/Validator/Constraints/AllowUniqueIdentifier.php: -------------------------------------------------------------------------------- 1 | getType()) { 19 | if (null === $customObject->getMasterObject()) { 20 | $this->context->buildViolation($constraint->missingMasterObject) 21 | ->atPath('masterObject') 22 | ->addViolation(); 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Helper/CsvHelper.php: -------------------------------------------------------------------------------- 1 | getParameters() as $key => $value) { 21 | $paramType = is_array($value) ? $toQueryBuilder->getConnection()::PARAM_STR_ARRAY : null; 22 | $toQueryBuilder->setParameter($key, $value, $paramType); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Helper/RandomHelper.php: -------------------------------------------------------------------------------- 1 | getWord(); 15 | } 16 | 17 | return ucfirst(implode(' ', $words)); 18 | } 19 | 20 | public function getString(int $limit): string 21 | { 22 | $string = ''; 23 | 24 | while (strlen($string) <= $limit) { 25 | $string .= $this->getWord(); 26 | } 27 | 28 | return substr($string, 0, $limit); 29 | } 30 | 31 | public function getWord(): string 32 | { 33 | return md5(uniqid()); 34 | } 35 | 36 | public function getEmail(): string 37 | { 38 | return uniqid('', true).'@'.uniqid('', true).'.net'; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Migrations/Version_0_0_1.php: -------------------------------------------------------------------------------- 1 | getTable($this->concatPrefix($this->table))->hasColumn('description'); 25 | } catch (SchemaException $e) { 26 | return false; 27 | } 28 | } 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | protected function up(): void 34 | { 35 | $this->addSql(" 36 | ALTER TABLE `{$this->concatPrefix($this->table)}` 37 | ADD `description` varchar(255) NULL AFTER `name_singular` 38 | "); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Migrations/Version_0_0_11.php: -------------------------------------------------------------------------------- 1 | concatPrefix($this->table); 25 | 26 | try { 27 | return !$schema->getTable($tableCustomObject)->hasColumn('type') || 28 | !$schema->getTable($tableCustomObject)->hasColumn('master_object'); 29 | } catch (SchemaException $e) { 30 | return false; 31 | } 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | protected function up(): void 38 | { 39 | $tableCustomObject = $this->concatPrefix($this->table); 40 | $default = CustomObject::TYPE_MASTER; 41 | 42 | $this->addSql("ALTER TABLE {$tableCustomObject} ADD type INT DEFAULT {$default}, ADD INDEX (type)"); 43 | $this->addSql("ALTER TABLE {$tableCustomObject} ADD master_object INT, ADD INDEX (master_object)"); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Migrations/Version_0_0_13.php: -------------------------------------------------------------------------------- 1 | getTable($this->concatPrefix('custom_object'))->hasColumn('relationship_object'); 20 | } catch (SchemaException $e) { 21 | return false; 22 | } 23 | } 24 | 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | protected function up(): void 29 | { 30 | $this->addSql("ALTER TABLE {$this->concatPrefix('custom_object')} ADD COLUMN `relationship_object` int(10) unsigned DEFAULT NULL AFTER `master_object`"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Migrations/Version_0_0_14.php: -------------------------------------------------------------------------------- 1 | getTable($this->concatPrefix('custom_field'))->hasColumn('show_in_custom_object_detail_list'); 20 | } catch (SchemaException $e) { 21 | return false; 22 | } 23 | } 24 | 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | protected function up(): void 29 | { 30 | $this->addSql("ALTER TABLE {$this->concatPrefix('custom_field')} ADD show_in_custom_object_detail_list TINYINT(1) DEFAULT 1 NOT NULL, ADD show_in_contact_detail_list TINYINT(1) DEFAULT 1 NOT NULL"); 31 | $this->addSql("UPDATE {$this->concatPrefix('custom_field')} SET show_in_custom_object_detail_list = 1, show_in_contact_detail_list = 1"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Migrations/Version_0_0_15.php: -------------------------------------------------------------------------------- 1 | getTable($this->concatPrefix('custom_object'))->hasColumn('relationship'); 17 | } catch (SchemaException $e) { 18 | return false; 19 | } 20 | } 21 | 22 | protected function up(): void 23 | { 24 | $this->addSql("ALTER TABLE {$this->concatPrefix('custom_object')} DROP COLUMN relationship"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Migrations/Version_0_0_16.php: -------------------------------------------------------------------------------- 1 | concatPrefix('custom_field'); 16 | $table = $schema->getTable($tableName); 17 | 18 | try { 19 | return $table->hasColumn('required') && 20 | null === $table->getColumn('required')->getDefault(); 21 | } catch (SchemaException $e) { 22 | return false; 23 | } 24 | } 25 | 26 | protected function up(): void 27 | { 28 | $this->addSql(" 29 | ALTER TABLE {$this->concatPrefix('custom_field')} CHANGE `required` `required` TINYINT(1) NOT NULL DEFAULT 0 30 | "); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Migrations/Version_0_0_17.php: -------------------------------------------------------------------------------- 1 | schema = $schema; 19 | try { 20 | return !$this->schema->getTable($this->concatPrefix('custom_object'))->hasForeignKey('FK_CO_RELATIONSHIP_OBJECT'); 21 | } catch (SchemaException $e) { 22 | return false; 23 | } 24 | } 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | protected function up(): void 30 | { 31 | // Beta instances are corrupted with index & foreign key through non-migration routes. It has to be managed through migrations. 32 | if ($this->schema->getTable($this->concatPrefix('custom_object'))->hasForeignKey('FK_45528188D74EDE85')) { 33 | $this->addSql("ALTER TABLE {$this->concatPrefix('custom_object')} DROP FOREIGN KEY FK_45528188D74EDE85;"); 34 | } 35 | 36 | if ($this->schema->getTable($this->concatPrefix('custom_object'))->hasIndex('UNIQ_45528188D74EDE85')) { 37 | $this->addSql("ALTER TABLE {$this->concatPrefix('custom_object')} DROP INDEX UNIQ_45528188D74EDE85;"); 38 | } 39 | $this->addSql("ALTER TABLE {$this->concatPrefix('custom_object')} ADD CONSTRAINT FK_CO_RELATIONSHIP_OBJECT FOREIGN KEY (relationship_object) REFERENCES {$this->concatPrefix('custom_object')} (id) ON DELETE SET NULL;"); 40 | $this->addSql("ALTER TABLE {$this->concatPrefix('custom_object')} ADD UNIQUE INDEX UNIQ_CO_RELATIONSHIP_OBJECT (relationship_object);"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Migrations/Version_0_0_2.php: -------------------------------------------------------------------------------- 1 | hasTable($this->concatPrefix($this->table)); 23 | } 24 | 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | protected function up(): void 29 | { 30 | $this->addSql("CREATE TABLE {$this->concatPrefix($this->table)} ( 31 | custom_item_id BIGINT UNSIGNED NOT NULL, 32 | parent_custom_item_id BIGINT UNSIGNED NOT NULL, 33 | date_added DATETIME NOT NULL COMMENT '(DC2Type:datetime)', 34 | {$this->generateIndexStatement($this->table, ['custom_item_id'])}, 35 | {$this->generateIndexStatement($this->table, ['parent_custom_item_id'])}, 36 | PRIMARY KEY(custom_item_id, parent_custom_item_id) 37 | ) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB 38 | "); 39 | 40 | $this->addSql($this->generateAlterTableForeignKeyStatement( 41 | $this->table, 42 | ['custom_item_id'], 43 | 'custom_item', 44 | ['id'], 45 | 'ON DELETE CASCADE' 46 | )); 47 | 48 | $this->addSql($this->generateAlterTableForeignKeyStatement( 49 | $this->table, 50 | ['parent_custom_item_id'], 51 | 'custom_item', 52 | ['id'], 53 | 'ON DELETE CASCADE' 54 | )); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Migrations/Version_0_0_21.php: -------------------------------------------------------------------------------- 1 | concatPrefix($this->table); 18 | 19 | try { 20 | return !$schema->getTable($tableCustomObject)->hasColumn('is_unique_identifier'); 21 | } catch (SchemaException $e) { 22 | return false; 23 | } 24 | } 25 | 26 | public function up(): void 27 | { 28 | $tableCustomObject = $this->concatPrefix($this->table); 29 | 30 | $this->addSql("ALTER TABLE {$tableCustomObject} ADD is_unique_identifier TINYINT(1) DEFAULT '0' NOT NULL"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Migrations/Version_0_0_22.php: -------------------------------------------------------------------------------- 1 | schema = $schema; 22 | 23 | return !$schema->hasTable($this->concatPrefix($this->table)); 24 | } 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | protected function up(): void 30 | { 31 | $customItemExportSchedulerTableName = $this->concatPrefix($this->table); 32 | $userIdColumnType = $this->getUserIdColumnType(); 33 | 34 | $this->addSql( 35 | "# Creating Table {$customItemExportSchedulerTableName} 36 | # ------------------------------------------------------------- 37 | CREATE TABLE {$customItemExportSchedulerTableName} ( 38 | id int(10) unsigned NOT NULL AUTO_INCREMENT, 39 | custom_object_id int(10) unsigned NOT NULL, 40 | user_id INT {$userIdColumnType} NOT NULL, 41 | PRIMARY KEY (id) 42 | ) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB 43 | "); 44 | } 45 | 46 | /** 47 | * @throws \Doctrine\DBAL\Schema\SchemaException 48 | */ 49 | private function getUserIdColumnType(): string 50 | { 51 | $usersTable = $this->schema->getTable($this->concatPrefix('users')); 52 | $column = $usersTable->getColumn('id'); 53 | 54 | return $column->getUnsigned() ? 'UNSIGNED' : 'SIGNED'; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Migrations/Version_0_0_23.php: -------------------------------------------------------------------------------- 1 | getTable($this->concatPrefix($this->table)); 20 | 21 | return !$table->hasColumn('scheduled_datetime'); 22 | } 23 | 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | protected function up(): void 28 | { 29 | $customItemExportSchedulerTableName = $this->concatPrefix($this->table); 30 | 31 | $this->addSql('ALTER TABLE '.$customItemExportSchedulerTableName." ADD scheduled_datetime DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)'"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Migrations/Version_0_0_27.php: -------------------------------------------------------------------------------- 1 | concatPrefix($this->table); 18 | 19 | try { 20 | return !$schema->getTable($tableCustomItem)->hasColumn('unique_hash'); 21 | } catch (SchemaException $e) { 22 | return false; 23 | } 24 | } 25 | 26 | public function up(): void 27 | { 28 | $tableCustomItem = $this->concatPrefix($this->table); 29 | $indexName = $this->generatePropertyName($this->table, 'UNIQ', ['unique_hash']); 30 | $this->addSql("ALTER TABLE {$tableCustomItem} ADD unique_hash VARCHAR(191), ADD UNIQUE KEY {$indexName} (unique_hash)"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Migrations/Version_0_0_3.php: -------------------------------------------------------------------------------- 1 | hasTable($this->concatPrefix($this->table)); 23 | } 24 | 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | protected function up(): void 29 | { 30 | $this->addSql("CREATE TABLE {$this->concatPrefix($this->table)} ( 31 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 32 | `custom_field_id` int(10) unsigned NOT NULL, 33 | `label` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 34 | `value` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 35 | PRIMARY KEY (`id`) 36 | ) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB 37 | "); 38 | 39 | $this->addSql($this->generateAlterTableForeignKeyStatement( 40 | $this->table, 41 | ['custom_field_id'], 42 | 'custom_field', 43 | ['id'], 44 | 'ON DELETE CASCADE' 45 | )); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Migrations/Version_0_0_4.php: -------------------------------------------------------------------------------- 1 | hasTable($this->concatPrefix($this->table)); 23 | } 24 | 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | protected function up(): void 29 | { 30 | $this->addSql("CREATE TABLE {$this->concatPrefix($this->table)} ( 31 | custom_field_id INT UNSIGNED NOT NULL, 32 | custom_item_id BIGINT UNSIGNED NOT NULL, 33 | option_id INT UNSIGNED NOT NULL, 34 | {$this->generateIndexStatement($this->table, ['custom_field_id'])}, 35 | {$this->generateIndexStatement($this->table, ['custom_item_id'])}, 36 | {$this->generateIndexStatement($this->table, ['option_id'])}, 37 | PRIMARY KEY(custom_field_id, custom_item_id, option_id) 38 | ) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB 39 | "); 40 | 41 | $this->addSql($this->generateAlterTableForeignKeyStatement( 42 | $this->table, 43 | ['custom_field_id'], 44 | 'custom_field', 45 | ['id'], 46 | 'ON DELETE CASCADE' 47 | )); 48 | 49 | $this->addSql($this->generateAlterTableForeignKeyStatement( 50 | $this->table, 51 | ['custom_item_id'], 52 | 'custom_item', 53 | ['id'], 54 | 'ON DELETE CASCADE' 55 | )); 56 | 57 | $this->addSql($this->generateAlterTableForeignKeyStatement( 58 | $this->table, 59 | ['option_id'], 60 | 'custom_field_option', 61 | ['id'], 62 | 'ON DELETE CASCADE' 63 | )); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Migrations/Version_0_0_5.php: -------------------------------------------------------------------------------- 1 | getTable($this->concatPrefix($this->table))->hasColumn('option_id'); 25 | } catch (SchemaException $e) { 26 | return false; 27 | } 28 | } 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | protected function up(): void 34 | { 35 | $this->addSql("ALTER TABLE {$this->concatPrefix($this->table)} 36 | CHANGE option_id value VARCHAR(255) NOT NULL"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Migrations/Version_0_0_6.php: -------------------------------------------------------------------------------- 1 | getTable($this->concatPrefix($this->table))->hasColumn('id'); 25 | } catch (SchemaException $e) { 26 | return false; 27 | } 28 | } 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | protected function up(): void 34 | { 35 | $this->addSql("ALTER TABLE {$this->concatPrefix($this->table)} MODIFY id INT UNSIGNED NOT NULL"); 36 | $this->addSql("ALTER TABLE {$this->concatPrefix($this->table)} DROP PRIMARY KEY"); 37 | $this->addSql("ALTER TABLE {$this->concatPrefix($this->table)} DROP id"); 38 | $this->addSql("ALTER TABLE {$this->concatPrefix($this->table)} ADD PRIMARY KEY (custom_field_id, value)"); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Migrations/Version_0_0_7.php: -------------------------------------------------------------------------------- 1 | getTable($this->concatPrefix($this->table))->hasColumn('option_order'); 25 | } catch (SchemaException $e) { 26 | return false; 27 | } 28 | } 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | protected function up(): void 34 | { 35 | $this->addSql("ALTER TABLE {$this->concatPrefix($this->table)} ADD option_order INT UNSIGNED NOT NULL"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Migrations/Version_0_0_8.php: -------------------------------------------------------------------------------- 1 | getTable($this->concatPrefix($this->table))->hasColumn('parent_custom_item_id'); 25 | } catch (SchemaException $e) { 26 | return false; 27 | } 28 | } 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | protected function up(): void 34 | { 35 | // There is no easy way to rename columns with primary and foreign keys and this table hasn't been used yet, 36 | // so let's delete it and create with proper column names. 37 | $this->addSql("DROP TABLE {$this->concatPrefix($this->table)}"); 38 | $this->addSql("CREATE TABLE {$this->concatPrefix($this->table)} ( 39 | custom_item_id_lower BIGINT UNSIGNED NOT NULL, 40 | custom_item_id_higher BIGINT UNSIGNED NOT NULL, 41 | date_added DATETIME NOT NULL COMMENT '(DC2Type:datetime)', 42 | PRIMARY KEY(custom_item_id_lower, custom_item_id_higher) 43 | ) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB 44 | "); 45 | 46 | $this->addSql($this->generateAlterTableForeignKeyStatement( 47 | $this->table, 48 | ['custom_item_id_lower'], 49 | 'custom_item', 50 | ['id'], 51 | 'ON DELETE CASCADE' 52 | )); 53 | 54 | $this->addSql($this->generateAlterTableForeignKeyStatement( 55 | $this->table, 56 | ['custom_item_id_higher'], 57 | 'custom_item', 58 | ['id'], 59 | 'ON DELETE CASCADE' 60 | )); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Migrations/Version_0_0_9.php: -------------------------------------------------------------------------------- 1 | getTable($this->concatPrefix($this->tableCustomObject))->hasColumn('alias'); 30 | } catch (SchemaException $e) { 31 | return false; 32 | } 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | protected function up(): void 39 | { 40 | $tableCustomObject = $this->concatPrefix($this->tableCustomObject); 41 | $this->addSql("ALTER TABLE {$tableCustomObject} ADD alias VARCHAR(255) NOT NULL, ADD INDEX (alias)"); 42 | 43 | $tableCustomField = $this->concatPrefix($this->tableCustomField); 44 | $this->addSql("ALTER TABLE {$tableCustomField} ADD alias VARCHAR(255) NOT NULL, ADD INDEX (alias)"); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Model/CustomFieldOptionModel.php: -------------------------------------------------------------------------------- 1 | entityManager = $entityManager; 23 | } 24 | 25 | /** 26 | * @todo Move this logic into repo. 27 | */ 28 | public function deleteByCustomFieldId(int $customFieldId): void 29 | { 30 | $queryBuilder = $this->entityManager->getConnection()->createQueryBuilder(); 31 | $queryBuilder->delete(MAUTIC_TABLE_PREFIX.'custom_field_option'); 32 | $queryBuilder->where('custom_field_id = :customFieldId'); 33 | $queryBuilder->setParameter('customFieldId', $customFieldId); 34 | $queryBuilder->execute(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Model/CustomItemXrefContactModel.php: -------------------------------------------------------------------------------- 1 | entityManager = $entityManager; 27 | $this->translator = $translator; 28 | } 29 | 30 | /** 31 | * @return mixed[] 32 | */ 33 | public function getLinksLineChartData( 34 | DateTime $from, 35 | DateTime $to, 36 | CustomItem $customItem 37 | ): array { 38 | $chart = new LineChart(null, $from, $to); 39 | $query = new ChartQuery($this->entityManager->getConnection(), $from, $to); 40 | $links = $query->fetchTimeData( 41 | 'custom_item_xref_contact', 42 | 'date_added', 43 | ['custom_item_id' => $customItem->getId()] 44 | ); 45 | $chart->setDataset($this->translator->trans('custom.item.linked.contacts'), $links); 46 | 47 | return $chart->render(); 48 | } 49 | 50 | /** 51 | * Used only by Mautic's generic methods. Use CustomItemPermissionProvider instead. 52 | */ 53 | public function getPermissionBase(): string 54 | { 55 | return 'custom_objects:custom_items'; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Provider/ConfigProvider.php: -------------------------------------------------------------------------------- 1 | coreParametersHelper = $coreParametersHelper; 27 | } 28 | 29 | /** 30 | * Returns true if the Custom Objects plugin is enabled. 31 | */ 32 | public function pluginIsEnabled(): bool 33 | { 34 | return (bool) $this->coreParametersHelper->get(self::CONFIG_PARAM_ENABLED, true); 35 | } 36 | 37 | public function isCustomObjectMergeFilterEnabled(): bool 38 | { 39 | return $this->coreParametersHelper->get('custom_object_merge_filter', false) 40 | && (0 === (int) $this->coreParametersHelper->get( 41 | self::CONFIG_PARAM_ITEM_VALUE_TO_CONTACT_RELATION_LIMIT, 42 | self::CONFIG_PARAM_ITEM_VALUE_TO_CONTACT_RELATION_DEFAULT_LIMIT 43 | ) 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Provider/CustomFieldPermissionProvider.php: -------------------------------------------------------------------------------- 1 | router = $router; 23 | } 24 | 25 | public function buildSaveRoute(string $fieldType, ?int $id = null, ?int $objectId = null, ?int $panelCount = null, ?int $panelId = null): string 26 | { 27 | $params['fieldType'] = $fieldType; 28 | 29 | if ($id) { 30 | $params['fieldId'] = $id; 31 | } 32 | 33 | if ($objectId) { 34 | $params['objectId'] = $objectId; 35 | } 36 | 37 | if ($panelCount) { 38 | $params['panelCount'] = $panelCount; 39 | } 40 | 41 | if (null !== $panelId) { 42 | $params['panelId'] = $panelId; 43 | } 44 | 45 | return $this->router->generate(static::ROUTE_SAVE, $params); 46 | } 47 | 48 | public function buildFormRoute(?int $id = null): string 49 | { 50 | $params = $id ? ['fieldId' => $id] : []; 51 | 52 | return $this->router->generate(static::ROUTE_FORM, $params); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Provider/CustomFieldTypeProvider.php: -------------------------------------------------------------------------------- 1 | customFieldTypes; 26 | } 27 | 28 | /** 29 | * @throws NotFoundException 30 | */ 31 | public function getType(string $key): CustomFieldTypeInterface 32 | { 33 | if (isset($this->customFieldTypes[$key])) { 34 | return $this->customFieldTypes[$key]; 35 | } 36 | 37 | throw new NotFoundException("Field type '{$key}' does not exist."); 38 | } 39 | 40 | public function addType(CustomFieldTypeInterface $customFieldType): void 41 | { 42 | $this->customFieldTypes[$customFieldType->getKey()] = $customFieldType; 43 | } 44 | 45 | /** 46 | * @return mixed[] 47 | */ 48 | public function getKeyTypeMapping(): array 49 | { 50 | $mapping = []; 51 | $types = $this->getTypes(); 52 | 53 | array_walk($types, function ($key, $val) use (&$mapping): void { 54 | /* @var AbstractCustomFieldType $key */ 55 | $mapping[$key::NAME] = $val; 56 | }); 57 | 58 | return $mapping; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Provider/CustomObjectPermissionProvider.php: -------------------------------------------------------------------------------- 1 | session = $session; 25 | $this->coreParametersHelper = $coreParametersHelper; 26 | } 27 | 28 | public function createObjectProvider(): SessionProvider 29 | { 30 | return $this->createProvider('custom-object'); 31 | } 32 | 33 | public function createItemProvider(int $objectId, string $filterEntityType = null, int $filterEntityId = null, bool $lookup = false): SessionProvider 34 | { 35 | $namespace = implode('-', ['custom-item', $objectId, $filterEntityType, $filterEntityId, (int) $lookup]); 36 | 37 | return $this->createProvider($namespace); 38 | } 39 | 40 | private function createProvider(string $namespace): SessionProvider 41 | { 42 | return new SessionProvider($this->session, $namespace, (int) $this->coreParametersHelper->get('default_pagelimit')); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Repository/CustomFieldRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder(CustomField::TABLE_ALIAS); 16 | $q->select('count('.CustomField::TABLE_ALIAS.'.id) as alias_count'); 17 | $q->where(CustomField::TABLE_ALIAS.'.alias = :alias'); 18 | $q->setParameter('alias', $alias); 19 | 20 | if (null !== $id) { 21 | $q->andWhere($q->expr()->neq(CustomField::TABLE_ALIAS.'.id', ':ignoreId')); 22 | $q->setParameter('ignoreId', $id); 23 | } 24 | 25 | return (bool) $q->getQuery()->getSingleResult()['alias_count']; 26 | } 27 | 28 | /** 29 | * @return ArrayCollection|CustomField[] 30 | */ 31 | public function getRequiredCustomFieldsForCustomObject(int $customObjectId): ArrayCollection 32 | { 33 | $queryBuilder = $this->createQueryBuilder(CustomField::TABLE_ALIAS); 34 | $queryBuilder->where(CustomField::TABLE_ALIAS.'.customObject = :customObjectId'); 35 | $queryBuilder->setParameter('customObjectId', $customObjectId); 36 | $queryBuilder->andWhere(CustomField::TABLE_ALIAS.'.required = :required'); 37 | $queryBuilder->setParameter('required', true); 38 | 39 | $query = $queryBuilder->getQuery(); 40 | 41 | return new ArrayCollection($query->getResult()); 42 | } 43 | 44 | public function getCustomFieldTypeById(int $customFieldId): string 45 | { 46 | $customField = $this->findOneBy(['id' => $customFieldId]); 47 | 48 | return $customField ? $customField->getType() : ''; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Repository/CustomItemExportSchedulerRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder(CustomItemXrefCustomItem::TABLE_ALIAS); 15 | $queryBuilder->delete(); 16 | $queryBuilder->where(CustomItemXrefCustomItem::TABLE_ALIAS.'.customItemLower = :customItemId'); 17 | $queryBuilder->orWhere(CustomItemXrefCustomItem::TABLE_ALIAS.'.customItemHigher = :customItemId'); 18 | $queryBuilder->setParameter('customItemId', $customItemId); 19 | $queryBuilder->getQuery()->execute(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Repository/DbalQueryTrait.php: -------------------------------------------------------------------------------- 1 | execute(); 20 | 21 | if ($statement instanceof Statement) { 22 | return $statement; 23 | } 24 | 25 | throw new \UnexpectedValueException(sprintf('Unexpected value of %s. Instance of %s expected.', print_r($statement, true), Statement::class)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Segment/Decorator/MultiselectDecorator.php: -------------------------------------------------------------------------------- 1 | getFilter() ?? []; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Segment/Query/Filter/CustomObjectMergedFilterQueryBuilder.php: -------------------------------------------------------------------------------- 1 | queryFilterHelper = $queryFilterHelper; 25 | } 26 | 27 | public static function getServiceId(): string 28 | { 29 | return 'mautic.lead.query.builder.custom_object.merged.value'; 30 | } 31 | 32 | public function applyQuery(QueryBuilder $queryBuilder, ContactSegmentFilter $filter): QueryBuilder 33 | { 34 | $leadsTableAlias = $queryBuilder->getTableAlias(MAUTIC_TABLE_PREFIX.'leads'); 35 | $subQuery = $this->queryFilterHelper->createMergeFilterQuery($filter, $leadsTableAlias); 36 | $queryBuilder->addLogic($queryBuilder->expr()->exists($subQuery->getSQL()), $filter->getGlue()); 37 | $queryBuilder->setParameters($subQuery->getParameters(), $subQuery->getParameterTypes()); 38 | 39 | return $queryBuilder; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Tests/Functional/DataFixtures/ORM/Data/custom_fields.yml: -------------------------------------------------------------------------------- 1 | MauticPlugin\CustomObjectsBundle\Entity\CustomField: 2 | custom_field1: 3 | custom_object: '@custom_object1' 4 | is_published: true 5 | date_added: '' 6 | label: ' Text' 7 | alias: 'text_1' 8 | type: 'text' 9 | custom_field2: 10 | custom_object: '@custom_object1' 11 | is_published: true 12 | date_added: '' 13 | label: ' Integer' 14 | alias: 'integer_2' 15 | type: 'int' 16 | custom_field3: 17 | custom_object: '@custom_object1' 18 | is_published: true 19 | date_added: '' 20 | label: ' Date' 21 | alias: 'date_3' 22 | type: 'date' 23 | custom_field4: 24 | custom_object: '@custom_object1' 25 | is_published: true 26 | date_added: '' 27 | label: ' DateTime' 28 | alias: 'datetime_4' 29 | type: 'datetime' 30 | custom_field5: 31 | custom_object: '@custom_object1' 32 | is_published: true 33 | date_added: '' 34 | label: ' Text' 35 | alias: 'text_5' 36 | type: 'text' 37 | custom_field6: 38 | custom_object: '@custom_object1' 39 | is_published: true 40 | date_added: '' 41 | label: ' Select' 42 | alias: 'select_6' 43 | type: 'select' 44 | custom_field8: 45 | custom_object: '@custom_object1' 46 | is_published: true 47 | date_added: '' 48 | label: ' Phone' 49 | alias: 'phone_8' 50 | type: 'phone' 51 | custom_field9: 52 | custom_object: '@custom_object1' 53 | is_published: true 54 | date_added: '' 55 | label: ' Hidden' 56 | alias: 'hidden_9' 57 | type: 'hidden' 58 | custom_field10: 59 | custom_object: '@custom_object1' 60 | is_published: true 61 | date_added: '' 62 | label: ' Email' 63 | alias: 'email_10' 64 | type: 'email' 65 | custom_field11: 66 | custom_object: '@custom_object1' 67 | is_published: true 68 | date_added: '' 69 | label: ' Country' 70 | alias: 'country_11' 71 | type: 'country' 72 | -------------------------------------------------------------------------------- /Tests/Functional/DataFixtures/ORM/Data/custom_items.yml: -------------------------------------------------------------------------------- 1 | MauticPlugin\CustomObjectsBundle\Entity\CustomItem: 2 | custom_itememotion: 3 | is_published: 1 4 | name: 'Object emotion' 5 | __construct: ['@custom_object1'] 6 | custom_itemattribute: 7 | is_published: 1 8 | name: 'Object attribute' 9 | __construct: ['@custom_object1'] 10 | custom_item{1..5}: 11 | is_published: 1 12 | name: 'Object ' 13 | __construct: ['@custom_object1'] 14 | custom_item{6..20}: 15 | is_published: 1 16 | name: 'Object ' 17 | __construct: ['@custom_object*'] 18 | custom_item{21}: 19 | is_published: 1 20 | name: 'No emotions' 21 | __construct: ['@custom_object3'] -------------------------------------------------------------------------------- /Tests/Functional/DataFixtures/ORM/Data/custom_objects.yml: -------------------------------------------------------------------------------- 1 | MauticPlugin\CustomObjectsBundle\Entity\CustomObject: 2 | custom_object1: 3 | is_published: 1 4 | date_added: '' 5 | name_singular: '' 6 | name_plural: '' 7 | description: '' 8 | alias: 'custom_object_1' 9 | custom_object2: 10 | is_published: 1 11 | date_added: '' 12 | name_singular: '' 13 | name_plural: '' 14 | description: '' 15 | alias: 'custom_object_2' 16 | custom_object3: 17 | is_published: 1 18 | date_added: '' 19 | name_singular: '' 20 | name_plural: '' 21 | description: '' 22 | alias: 'custom_object_3' 23 | -------------------------------------------------------------------------------- /Tests/Functional/DataFixtures/ORM/Data/custom_values.yml: -------------------------------------------------------------------------------- 1 | MauticPlugin\CustomObjectsBundle\Entity\CustomFieldValueText: 2 | custom_field_value_text1: 3 | __construct: ['@custom_field1', '@custom_itememotion','love'] 4 | custom_field_value_text2: 5 | __construct: ['@custom_field1', '@custom_item1','love'] 6 | custom_field_value_text3: 7 | __construct: ['@custom_field1', '@custom_item2','hate'] 8 | custom_field_value_text4: 9 | __construct: ['@custom_field1', '@custom_item3','sadness'] 10 | custom_field_value_text5: 11 | __construct: ['@custom_field1', '@custom_item4','love'] 12 | custom_field_value_text6: 13 | __construct: ['@custom_field1', '@custom_item5','hate'] 14 | 15 | -------------------------------------------------------------------------------- /Tests/Functional/DataFixtures/ORM/Data/custom_xref.yml: -------------------------------------------------------------------------------- 1 | MauticPlugin\CustomObjectsBundle\Entity\CustomItemXrefContact: 2 | custom_item_xref1: 3 | __construct: ['@custom_itememotion', '@contact1'] 4 | custom_item_xref2: 5 | __construct: ['@custom_item1', '@contact2'] 6 | custom_item_xref3: 7 | __construct: ['@custom_item2', '@contact3'] 8 | custom_item_xref4: 9 | __construct: ['@custom_item3', '@contact4'] 10 | custom_item_xref5: 11 | __construct: ['@custom_item4', '@contact5'] 12 | custom_item_xref6: 13 | __construct: ['@custom_item5', '@contact6'] 14 | custom_item_xref7: 15 | __construct: ['@custom_item21', '@contact3'] 16 | custom_item_xref8: 17 | __construct: ['@custom_item21', '@contact2'] 18 | -------------------------------------------------------------------------------- /Tests/Functional/DataFixtures/ORM/Data/leads.yml: -------------------------------------------------------------------------------- 1 | Mautic\LeadBundle\Entity\Lead: 2 | contact{1..10}: 3 | firstname: '' 4 | lastname: '' 5 | is_published: 1 6 | date_added: '' 7 | email: '' -------------------------------------------------------------------------------- /Tests/Functional/DataFixtures/ORM/Data/roles.yml: -------------------------------------------------------------------------------- 1 | Mautic\UserBundle\Entity\Role: 2 | role1: 3 | is_published: 1 4 | name: 'Test Role' 5 | description: '' 6 | is_admin: 1 -------------------------------------------------------------------------------- /Tests/Functional/DataFixtures/ORM/Data/users.yml: -------------------------------------------------------------------------------- 1 | Mautic\UserBundle\Entity\User: 2 | user1: 3 | role: '@role1' 4 | is_published: 1 5 | username: 'test2' 6 | password: 'dxul9wuermx9l' 7 | first_name: '' 8 | last_name: '' 9 | email: '' 10 | 11 | -------------------------------------------------------------------------------- /Tests/Functional/EventListener/DynamicContentSubscriberTest.php: -------------------------------------------------------------------------------- 1 | assertSame('evaluateFilters', $methodName); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Tests/Functional/EventListener/SegmentFiltersDictionarySubscriberTest.php: -------------------------------------------------------------------------------- 1 | getFixturesDirectory(); 21 | $objects = $this->loadFixtureFiles([ 22 | $fixturesDirectory.'/leads.yml', 23 | $fixturesDirectory.'/custom_objects.yml', 24 | $fixturesDirectory.'/custom_fields.yml', 25 | $fixturesDirectory.'/custom_items.yml', 26 | $fixturesDirectory.'/custom_xref.yml', 27 | $fixturesDirectory.'/custom_values.yml', 28 | ]); 29 | $this->setFixtureObjects($objects); 30 | 31 | /** @var ConfigProvider|MockObject $configProviderMock */ 32 | $configProviderMock = $this->createMock(ConfigProvider::class); 33 | $configProviderMock->expects($this->once())->method('pluginIsEnabled')->willReturn(true); 34 | 35 | $event = new SegmentDictionaryGenerationEvent(); 36 | 37 | $subscriber = new SegmentFiltersDictionarySubscriber($this->getContainer()->get('doctrine'), $configProviderMock); 38 | $subscriber->onGenerateSegmentDictionary($event); 39 | 40 | $COName = 'cmo_'.$this->getFixtureById('custom_object3')->getId(); 41 | $this->assertTrue($event->hasTranslation($COName)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Tests/Functional/Exception/FixtureNotFoundException.php: -------------------------------------------------------------------------------- 1 | createMock(Request::class); 22 | 23 | $map = [ 24 | ['objectId', null, $objectId], 25 | ['fieldId', null, $fieldId], 26 | ['fieldType', null, $fieldType], 27 | ['panelId', null, $panelId], 28 | ['panelCount', null, $panelCount], 29 | ]; 30 | 31 | foreach ($mapExtras as $mapExtra) { 32 | $map[] = $mapExtra; 33 | } 34 | 35 | $request 36 | ->method('get') 37 | ->willReturnMap($map); 38 | 39 | $query = $this->createMock(ParameterBag::class); 40 | $query->method('get') 41 | ->willReturn($fieldId); 42 | $request->query = $query; 43 | 44 | return $request; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Tests/Unit/Controller/CustomItem/ExportControllerTest.php: -------------------------------------------------------------------------------- 1 | createMockData(); 25 | $url = '/s/custom/object/'.$this->customObject->getId().'/export'; 26 | $crawler = $this->client->request(Request::METHOD_POST, $url); 27 | $clientResponse = $this->client->getResponse(); 28 | $clientResponseContent = $clientResponse->getContent(); 29 | 30 | $this->assertEquals(Response::HTTP_OK, $clientResponse->getStatusCode()); 31 | $customItemExportScheduler = $this->em->getRepository(CustomItemExportScheduler::class) 32 | ->findBy(['customObjectId' => $this->customObject->getId()]); 33 | 34 | $this->assertNotNull($customItemExportScheduler, 'Custom Item Export Not Scheduled'); 35 | $this->assertStringContainsString('Custom Item export scheduled.', $clientResponseContent); 36 | } 37 | 38 | /** 39 | * @throws \Doctrine\ORM\ORMException 40 | * @throws \Doctrine\ORM\OptimisticLockException 41 | */ 42 | private function createMockData(): void 43 | { 44 | $customObject = $this->createCustomObject(); 45 | $this->em->persist($customObject); 46 | $this->em->flush(); 47 | 48 | $this->customObject = $customObject; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Tests/Unit/CustomFieldType/CountryTypeTest.php: -------------------------------------------------------------------------------- 1 | translator = $this->createMock(TranslatorInterface::class); 27 | $this->fieldType = new CountryType( 28 | $this->translator, 29 | $this->createMock(FilterOperatorProviderInterface::class) 30 | ); 31 | } 32 | 33 | public function testGetSymfonyFormFieldType(): void 34 | { 35 | $this->assertSame( 36 | \Symfony\Component\Form\Extension\Core\Type\CountryType::class, 37 | $this->fieldType->getSymfonyFormFieldType() 38 | ); 39 | } 40 | 41 | public function testGetEntityClass(): void 42 | { 43 | $this->assertSame( 44 | CustomFieldValueText::class, 45 | $this->fieldType->getEntityClass() 46 | ); 47 | } 48 | 49 | public function testConfigureOptions(): void 50 | { 51 | $optionsResolver = $this->createMock(OptionsResolver::class); 52 | 53 | $optionsResolver->expects($this->once()) 54 | ->method('setDefaults') 55 | ->with($this->callback(function (array $options) { 56 | $this->assertFalse($options['choice_translation_domain']); 57 | $this->assertSame('Czech Republic', $options['choices']['choices']['Czech Republic']); 58 | 59 | return true; 60 | })); 61 | 62 | $this->fieldType->configureOptions($optionsResolver); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Tests/Unit/CustomFieldType/DataTransformer/CsvTransformerTest.php: -------------------------------------------------------------------------------- 1 | assertSame('', $transformer->transform(null)); 16 | $this->assertSame('', $transformer->transform([])); 17 | $this->assertSame('some,array,"contains, comma"', $transformer->transform(['some', 'array', 'contains, comma'])); 18 | $this->assertSame('some string', $transformer->transform('some string')); 19 | 20 | $this->assertSame([], $transformer->reverseTransform(null)); 21 | $this->assertSame([], $transformer->reverseTransform('')); 22 | $this->assertSame(['some', 'array', 'contains, comma'], $transformer->reverseTransform('some,array,"contains, comma"')); 23 | $this->assertSame(['some', 'array'], $transformer->reverseTransform(['some', 'array'])); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tests/Unit/CustomFieldType/DataTransformer/DateTimeAtomTransformerTest.php: -------------------------------------------------------------------------------- 1 | assertNull($transformer->transform(null)); 21 | $this->assertSame($date, $transformer->transform($date)->format($format)); 22 | 23 | $this->assertNull($transformer->reverseTransform(null)); 24 | $this->assertSame($date, $transformer->reverseTransform($date)); 25 | $this->assertSame($date, $transformer->reverseTransform($datetime)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Tests/Unit/CustomFieldType/DataTransformer/DateTimeTransformerTest.php: -------------------------------------------------------------------------------- 1 | assertNull($transformer->transform(null)); 20 | $this->assertSame($date, $transformer->transform($date)->format($format)); 21 | 22 | $this->assertNull($transformer->reverseTransform(null)); 23 | $this->assertSame($date, $transformer->reverseTransform($date)); 24 | $this->assertSame($date, $transformer->reverseTransform($datetime)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/Unit/CustomFieldType/DataTransformer/DateTransformerTest.php: -------------------------------------------------------------------------------- 1 | assertNull($transformer->transform(null)); 20 | $this->assertSame($date, $transformer->transform($date)->format($format)); 21 | 22 | $this->assertNull($transformer->reverseTransform(null)); 23 | $this->assertSame($date, $transformer->reverseTransform($date)); // Transform string 24 | $this->assertSame($date, $transformer->reverseTransform($datetime)); // Transform DateTime object 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/Unit/CustomFieldType/DataTransformer/MultivalueTransformerTest.php: -------------------------------------------------------------------------------- 1 | assertSame([], $transformer->transform(null)); 19 | $this->assertSame($value, $transformer->transform($transformedValue)); 20 | 21 | $this->assertNull($transformer->reverseTransform(null)); 22 | $this->assertSame($transformedValue, $transformer->reverseTransform($value)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/Unit/CustomFieldType/DataTransformer/ViewDateTransformerTest.php: -------------------------------------------------------------------------------- 1 | assertNull($transformer->transform('')); 18 | $this->assertSame(self::VAL, $transformer->transform(self::VAL)); 19 | } 20 | 21 | public function testReverseTransform(): void 22 | { 23 | $transformer = new ViewDateTransformer(); 24 | 25 | $this->assertSame('', $transformer->reverseTransform(null)); 26 | $this->assertSame(self::VAL, $transformer->reverseTransform(self::VAL)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Tests/Unit/CustomFieldType/EmailTypeTest.php: -------------------------------------------------------------------------------- 1 | translator = $this->createMock(TranslatorInterface::class); 27 | $this->customField = $this->createMock(CustomField::class); 28 | $this->fieldType = new EmailType( 29 | $this->translator, 30 | $this->createMock(FilterOperatorProviderInterface::class) 31 | ); 32 | } 33 | 34 | public function testValidateValueWithBogusString(): void 35 | { 36 | $this->translator->expects($this->once()) 37 | ->method('trans') 38 | ->with('custom.field.email.invalid', ['%value%' => 'unicorn'], 'validators') 39 | ->willReturn('Translated message'); 40 | 41 | $this->expectException(\UnexpectedValueException::class); 42 | $this->fieldType->validateValue($this->customField, 'unicorn'); 43 | } 44 | 45 | public function testValidateValueWithEmptyString(): void 46 | { 47 | $this->fieldType->validateValue($this->customField, ''); 48 | // No exception means it passes 49 | $this->addToAssertionCount(1); 50 | } 51 | 52 | public function testValidateValueWithValidEmailAddress(): void 53 | { 54 | $this->fieldType->validateValue($this->customField, 'hello@mautic.org'); 55 | // No exception means it passes 56 | $this->addToAssertionCount(1); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/Unit/CustomFieldType/MultiselectTypeTest.php: -------------------------------------------------------------------------------- 1 | translator = $this->createMock(TranslatorInterface::class); 26 | $this->fieldType = new MultiselectType( 27 | $this->translator, 28 | $this->createMock(FilterOperatorProviderInterface::class), 29 | new CsvHelper() 30 | ); 31 | } 32 | 33 | public function testUsePlaceholder(): void 34 | { 35 | $this->assertTrue($this->fieldType->usePlaceholder()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/Unit/CustomFieldType/UrlTypeTest.php: -------------------------------------------------------------------------------- 1 | translator = $this->createMock(TranslatorInterface::class); 27 | $this->customField = $this->createMock(CustomField::class); 28 | $this->fieldType = new UrlType( 29 | $this->translator, 30 | $this->createMock(FilterOperatorProviderInterface::class) 31 | ); 32 | } 33 | 34 | public function testValidateValueWithBogusString(): void 35 | { 36 | $this->translator->expects($this->once()) 37 | ->method('trans') 38 | ->with('custom.field.url.invalid', ['%value%' => 'unicorn'], 'validators') 39 | ->willReturn('Translated message'); 40 | 41 | $this->expectException(\UnexpectedValueException::class); 42 | $this->fieldType->validateValue($this->customField, 'unicorn'); 43 | } 44 | 45 | public function testValidateValueWithEmptyString(): void 46 | { 47 | $this->fieldType->validateValue($this->customField, ''); 48 | // No exception means it passes 49 | $this->addToAssertionCount(1); 50 | } 51 | 52 | public function testValidateValueWithValidUrl(): void 53 | { 54 | $this->fieldType->validateValue($this->customField, 'https://mautic.org'); 55 | // No exception means it passes 56 | $this->addToAssertionCount(1); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/Unit/DTO/TokenTest.php: -------------------------------------------------------------------------------- 1 | assertSame($tokenString, $token->getToken()); 16 | 17 | // Test default values 18 | $this->assertSame(1, $token->getLimit()); 19 | $this->assertSame('', $token->getWhere()); 20 | $this->assertSame('latest', $token->getOrder()); 21 | $this->assertSame('', $token->getDefaultValue()); 22 | $this->assertSame('', $token->getFormat()); 23 | $this->assertSame('', $token->getCustomFieldAlias()); 24 | $this->assertSame('', $token->getCustomObjectAlias()); 25 | 26 | // Test getters & setters 27 | $token->setLimit(2); 28 | $this->assertSame(2, $token->getLimit()); 29 | 30 | $token->setWhere('somewhere'); 31 | $this->assertSame('somewhere', $token->getWhere()); 32 | 33 | $token->setOrder('someorder'); 34 | $this->assertSame('someorder', $token->getOrder()); 35 | 36 | $token->setDefaultValue('someDefaultValue'); 37 | $this->assertSame('someDefaultValue', $token->getDefaultValue()); 38 | 39 | $token->setFormat('someFormat'); 40 | $this->assertSame('someFormat', $token->getFormat()); 41 | 42 | $token->setCustomFieldAlias('someCustomFieldAlias'); 43 | $this->assertSame('someCustomFieldAlias', $token->getCustomFieldAlias()); 44 | 45 | $token->setCustomObjectAlias('someCuastomFieldObjectAlias'); 46 | $this->assertSame('someCuastomFieldObjectAlias', $token->getCustomObjectAlias()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Tests/Unit/DependencyInjection/Compiler/CustomFieldTypePassTest.php: -------------------------------------------------------------------------------- 1 | createMock(ContainerBuilder::class); 16 | $definition = $this->createMock(Definition::class); 17 | $customFieldTypePass = new CustomFieldTypePass(); 18 | 19 | $containerBuilder->expects($this->once()) 20 | ->method('findTaggedServiceIds') 21 | ->with('custom.field.type') 22 | ->willReturn(['int.type' => [], 'text.type' => []]); 23 | 24 | $containerBuilder->expects($this->exactly(3)) 25 | ->method('findDefinition') 26 | ->withConsecutive( 27 | ['custom_field.type.provider'], 28 | ['int.type'], 29 | ['text.type'] 30 | ) 31 | ->willReturnOnConsecutiveCalls( 32 | $definition 33 | ); 34 | 35 | $definition->expects($this->exactly(2)) 36 | ->method('addMethodCall') 37 | ->withConsecutive( 38 | ['addType'], 39 | ['addType'] 40 | ); 41 | 42 | $customFieldTypePass->process($containerBuilder); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Tests/Unit/Entity/CustomField/ParamsTest.php: -------------------------------------------------------------------------------- 1 | $placeholder, 17 | ]; 18 | 19 | $params = new Params($paramsArray); 20 | 21 | $this->assertSame($placeholder, $params->getPlaceholder()); 22 | $this->assertSame($paramsArray, $params->__toArray()); 23 | } 24 | 25 | public function testToArrayRemovingFalseAndNullValues() 26 | { 27 | $placeholder = null; 28 | 29 | $paramsArray = [ 30 | 'placeholder' => $placeholder, 31 | ]; 32 | 33 | $params = new Params($paramsArray); 34 | 35 | $this->assertSame(array_filter($paramsArray), $params->__toArray()); 36 | } 37 | 38 | public function testGettersSetters() 39 | { 40 | $placeholder = 'placeholder'; 41 | 42 | $params = new Params(); 43 | 44 | $params->setPlaceholder($placeholder); 45 | $this->assertSame($placeholder, $params->getPlaceholder()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Tests/Unit/Entity/CustomFieldFactoryTest.php: -------------------------------------------------------------------------------- 1 | definedTypes as $type) { 27 | $typeClass = ucfirst($type); 28 | $typeClass = "\MauticPlugin\CustomObjectsBundle\CustomFieldType\\{$typeClass}Type"; 29 | 30 | $typeClass = $this->createMock($typeClass); 31 | $typeClass->expects($this->once()) 32 | ->method('getKey') 33 | ->willReturn($type); 34 | 35 | $typeProvider = $this->createMock(CustomFieldTypeProvider::class); 36 | $typeProvider 37 | ->expects($this->once()) 38 | ->method('getType') 39 | ->willReturn($typeClass); 40 | 41 | $factory = new CustomFieldFactory($typeProvider); 42 | 43 | $customField = $factory->create($type, $customObject); 44 | $this->assertSame($type, $customField->getTypeObject()->getKey()); 45 | $this->assertSame($customObject, $customField->getCustomObject()); 46 | } 47 | 48 | $typeProvider = $this->createMock(CustomFieldTypeProvider::class); 49 | $typeProvider 50 | ->expects($this->once()) 51 | ->method('getType') 52 | ->will($this->throwException(new NotFoundException())); 53 | 54 | $factory = new CustomFieldFactory($typeProvider); 55 | 56 | $this->expectException(NotFoundException::class); 57 | $factory->create('undefined_type', $customObject); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Tests/Unit/Entity/CustomFieldValueDateTest.php: -------------------------------------------------------------------------------- 1 | assertSame($customField, $optionValue->getCustomField()); 22 | $this->assertSame($customItem, $optionValue->getCustomItem()); 23 | $this->assertSame($value, $optionValue->getValue()); 24 | 25 | $optionValue->setValue(null); 26 | 27 | $this->assertNull($optionValue->getValue()); 28 | 29 | $optionValue->setValue(''); 30 | 31 | $this->assertNull($optionValue->getValue()); 32 | 33 | $optionValue->setValue($value); 34 | 35 | $this->assertSame($value, $optionValue->getValue()); 36 | 37 | $optionValue->setValue('2019-06-23'); 38 | 39 | $this->assertSame('2019-06-23T00:00:00+00:00', $optionValue->getValue()->format(DATE_ATOM)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Tests/Unit/Entity/CustomFieldValueDateTimeTest.php: -------------------------------------------------------------------------------- 1 | assertSame($customField, $optionValue->getCustomField()); 22 | $this->assertSame($customItem, $optionValue->getCustomItem()); 23 | $this->assertSame($value, $optionValue->getValue()); 24 | 25 | $optionValue->setValue(null); 26 | 27 | $this->assertNull($optionValue->getValue()); 28 | 29 | $optionValue->setValue(''); 30 | 31 | $this->assertNull($optionValue->getValue()); 32 | 33 | $optionValue->setValue($value); 34 | 35 | $this->assertSame($value, $optionValue->getValue()); 36 | 37 | $optionValue->setValue('2019-06-23 12:43:33'); 38 | 39 | $this->assertSame('2019-06-23T12:43:33+00:00', $optionValue->getValue()->format(DATE_ATOM)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Tests/Unit/Entity/CustomFieldValueOptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame($customField, $optionValue->getCustomField()); 24 | $this->assertSame($customItem, $optionValue->getCustomItem()); 25 | $this->assertSame($value1, $optionValue->getValue()); 26 | 27 | $optionValue->setValue($value2); 28 | 29 | $this->assertSame($value2, $optionValue->getValue()); 30 | 31 | $optionValue = new CustomFieldValueOption($customField, $customItem); 32 | 33 | $optionValue->addValue($value1); 34 | $optionValue->addValue($value1); // Test uniqueness. 35 | $optionValue->addValue($value2); 36 | 37 | $this->assertSame([$value1, $value2], $optionValue->getValue()); 38 | } 39 | 40 | public function testSetValueMustHaveUniqueOptions(): void 41 | { 42 | $optionValue = new CustomFieldValueOption( 43 | new CustomField(), 44 | new CustomItem(new CustomObject()) 45 | ); 46 | 47 | $optionValue->setValue(['red', 'blue', 'red']); 48 | 49 | $this->assertSame(['red', 'blue'], $optionValue->getValue()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Tests/Unit/Entity/CustomItemXrefCompanyTest.php: -------------------------------------------------------------------------------- 1 | createMock(CustomItem::class); 17 | $company = $this->createMock(Company::class); 18 | $dateAdded = new DateTimeImmutable('2019-03-04 12:34:56'); 19 | $xref = new CustomItemXrefCompany( 20 | $customItem, 21 | $company, 22 | $dateAdded 23 | ); 24 | 25 | $this->assertSame($customItem, $xref->getCustomItem()); 26 | $this->assertSame($company, $xref->getCompany()); 27 | $this->assertSame($company, $xref->getLinkedEntity()); 28 | $this->assertSame($dateAdded, $xref->getDateAdded()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tests/Unit/Entity/CustomItemXrefContactTest.php: -------------------------------------------------------------------------------- 1 | createMock(CustomItem::class); 17 | $contact = $this->createMock(Lead::class); 18 | $dateAdded = new DateTimeImmutable('2019-03-04 12:34:56'); 19 | $xref = new CustomItemXrefContact( 20 | $customItem, 21 | $contact, 22 | $dateAdded 23 | ); 24 | 25 | $this->assertSame($customItem, $xref->getCustomItem()); 26 | $this->assertSame($contact, $xref->getContact()); 27 | $this->assertSame($contact, $xref->getLinkedEntity()); 28 | $this->assertSame($dateAdded, $xref->getDateAdded()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tests/Unit/Event/CustomItemEventTest.php: -------------------------------------------------------------------------------- 1 | createMock(CustomItem::class); 15 | $event = new CustomItemEvent($customItem); 16 | 17 | $this->assertSame($customItem, $event->getCustomItem()); 18 | $this->assertFalse($event->entityIsNew()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Tests/Unit/Event/CustomItemListDbalQueryEventTest.php: -------------------------------------------------------------------------------- 1 | createMock(QueryBuilder::class); 16 | $tableConfig = $this->createMock(TableConfig::class); 17 | $event = new CustomItemListDbalQueryEvent($queryBuilder, $tableConfig); 18 | 19 | $this->assertSame($queryBuilder, $event->getQueryBuilder()); 20 | $this->assertSame($tableConfig, $event->getTableConfig()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Tests/Unit/Event/CustomItemListQueryEventTest.php: -------------------------------------------------------------------------------- 1 | createMock(QueryBuilder::class); 16 | $tableConfig = $this->createMock(TableConfig::class); 17 | $event = new CustomItemListQueryEvent($queryBuilder, $tableConfig); 18 | 19 | $this->assertSame($queryBuilder, $event->getQueryBuilder()); 20 | $this->assertSame($tableConfig, $event->getTableConfig()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Tests/Unit/Event/CustomItemXrefEntityEventTest.php: -------------------------------------------------------------------------------- 1 | createMock(CustomItemXrefContact::class); 15 | $event = new CustomItemXrefEntityEvent($xref); 16 | 17 | $this->assertSame($xref, $event->getXref()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/Unit/Event/CustomObjectEventTest.php: -------------------------------------------------------------------------------- 1 | customObject = new CustomObject(); 32 | $this->event = new CustomObjectEvent($this->customObject); 33 | $this->flashBag = $this->createMock(FlashBag::class); 34 | } 35 | 36 | public function testGettersSetters(): void 37 | { 38 | static::assertSame($this->customObject, $this->event->getCustomObject()); 39 | static::assertFalse($this->event->entityIsNew()); 40 | } 41 | 42 | public function testFlashBagGettersAndSetters(): void 43 | { 44 | $this->event->setFlashBag($this->flashBag); 45 | static::assertSame($this->flashBag, $this->event->getFlashBag()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Tests/Unit/Event/CustomObjectListFormatEventTest.php: -------------------------------------------------------------------------------- 1 | assertSame([], $event->getCustomObjectValues()); 17 | $this->assertSame('default', $event->getFormat()); 18 | $this->assertSame('', $event->getFormattedString()); 19 | $this->assertFalse($event->hasBeenFormatted()); 20 | 21 | // Test format setter 22 | $event->setFormattedString(''); 23 | $this->assertSame('', $event->getFormattedString()); 24 | $this->assertFalse($event->hasBeenFormatted()); 25 | 26 | $event->setFormattedString('a formatted string'); 27 | $this->assertSame('a formatted string', $event->getFormattedString()); 28 | $this->assertTrue($event->hasBeenFormatted()); 29 | 30 | $event->setFormattedString(''); 31 | $this->assertSame('a formatted string', $event->getFormattedString()); 32 | $this->assertTrue($event->hasBeenFormatted()); 33 | 34 | // Test custom construct 35 | $event = new CustomObjectListFormatEvent(['someValues'], 'someFormat'); 36 | $this->assertSame(['someValues'], $event->getCustomObjectValues()); 37 | $this->assertSame('someFormat', $event->getFormat()); 38 | $this->assertSame('', $event->getFormattedString()); 39 | $this->assertFalse($event->hasBeenFormatted()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Tests/Unit/Exception/InvalidCustomObjectFormatListExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 15 | "'test' is not a valid custom object list format.", 16 | $exception->getMessage() 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/Unit/Exception/InvalidValueExceptionTest.php: -------------------------------------------------------------------------------- 1 | createMock(CustomField::class); 16 | 17 | $this->assertNull($exception->getCustomField()); 18 | 19 | $exception->setCustomField($customField); 20 | 21 | $this->assertSame($customField, $exception->getCustomField()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/Unit/Form/Validator/AllowUniqueIdentifierTest.php: -------------------------------------------------------------------------------- 1 | constraint = new AllowUniqueIdentifier(); 19 | } 20 | 21 | public function testValidatedBy(): void 22 | { 23 | $this->assertSame(AllowUniqueIdentifierValidator::class, $this->constraint->validatedBy()); 24 | } 25 | 26 | public function testGetTargets(): void 27 | { 28 | $this->assertSame(AllowUniqueIdentifier::CLASS_CONSTRAINT, $this->constraint->getTargets()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tests/Unit/Form/Validator/CustomObjectTypeValuesTest.php: -------------------------------------------------------------------------------- 1 | constraint = new CustomObjectTypeValues(); 20 | } 21 | 22 | public function testValidatedBy() 23 | { 24 | $this->assertSame(CustomObjectTypeValuesValidator::class, $this->constraint->validatedBy()); 25 | } 26 | 27 | public function testGetTargets() 28 | { 29 | $this->assertSame(CustomObjectTypeValues::CLASS_CONSTRAINT, $this->constraint->getTargets()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Tests/Unit/Helper/CsvHelperTest.php: -------------------------------------------------------------------------------- 1 | assertSame('', $csvHelper->arrayToCsvLine([])); 16 | $this->assertSame('0,ff,,-4,,1,"text, with; ""comma"""', $csvHelper->arrayToCsvLine([0, 'ff', null, -4, false, true, 'text, with; "comma"'])); 17 | } 18 | 19 | public function testCsvLineToArray(): void 20 | { 21 | $csvHelper = new CsvHelper(); 22 | 23 | $this->assertSame([], $csvHelper->csvLineToArray('')); 24 | $this->assertSame(['0', 'ff', '', '-4', '', '1', 'text, with; \"comma\"'], $csvHelper->csvLineToArray('0,ff,,-4,,1, "text, with; \"comma\""')); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/Unit/Model/CustomFieldOptionModelTest.php: -------------------------------------------------------------------------------- 1 | entityManager = $this->createMock(EntityManager::class); 30 | $this->connection = $this->createMock(Connection::class); 31 | $this->queryBuilder = $this->createMock(QueryBuilder::class); 32 | $this->customFieldOptionModel = new CustomFieldOptionModel($this->entityManager); 33 | 34 | $this->entityManager->method('getConnection')->willReturn($this->connection); 35 | $this->connection->method('createQueryBuilder')->willReturn($this->queryBuilder); 36 | } 37 | 38 | public function testDeleteByCustomFieldId(): void 39 | { 40 | $this->queryBuilder->expects($this->once()) 41 | ->method('delete') 42 | ->with(MAUTIC_TABLE_PREFIX.'custom_field_option'); 43 | 44 | $this->queryBuilder->expects($this->once()) 45 | ->method('where') 46 | ->with('custom_field_id = :customFieldId'); 47 | 48 | $this->queryBuilder->expects($this->once()) 49 | ->method('setParameter') 50 | ->with('customFieldId', 123); 51 | 52 | $this->queryBuilder->expects($this->once()) 53 | ->method('execute'); 54 | 55 | $this->customFieldOptionModel->deleteByCustomFieldId(123); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Tests/Unit/Repository/DbalQueryTraitTest.php: -------------------------------------------------------------------------------- 1 | createMock(QueryBuilder::class); 16 | $statement = $this->createMock(Statement::class); 17 | $trait = $this->getMockForTrait(DbalQueryTrait::class); 18 | 19 | $qb->expects($this->once()) 20 | ->method('execute') 21 | ->willReturn($statement); 22 | 23 | $this->assertSame($statement, $this->invokeMethod($trait, 'executeSelect', [$qb])); 24 | } 25 | 26 | public function testExecuteSelectIfNotSelect(): void 27 | { 28 | $qb = $this->createMock(QueryBuilder::class); 29 | $trait = $this->getMockForTrait(DbalQueryTrait::class); 30 | 31 | $qb->expects($this->once()) 32 | ->method('execute') 33 | ->willReturn(4); 34 | 35 | $this->expectException(\UnexpectedValueException::class); 36 | 37 | $this->invokeMethod($trait, 'executeSelect', [$qb]); 38 | } 39 | 40 | /** 41 | * @param mixed[] $args 42 | * 43 | * @return mixed 44 | */ 45 | private function invokeMethod(object $object, string $methodName, array $args = []) 46 | { 47 | $reflection = new \ReflectionClass(get_class($object)); 48 | $method = $reflection->getMethod($methodName); 49 | $method->setAccessible(true); 50 | 51 | return $method->invokeArgs($object, $args); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Tests/Unit/Segment/Query/Filter/CustomFieldFilterQueryBuilderTest.php: -------------------------------------------------------------------------------- 1 | assertSame('mautic.lead.query.builder.custom_field.value', CustomFieldFilterQueryBuilder::getServiceId()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Translations/en_US/flashes.ini: -------------------------------------------------------------------------------- 1 | custom.item.error.link.exists.already="The custom item %itemId% is already linked to %entityType% %entityId%" 2 | custom.item.error.items.not.found="Items %ids% were not found" 3 | custom.item.error.items.denied="The action has been denied for items %ids%" 4 | custom.item.unlinked="%itemName% (%itemId%) was unlinked from %entityType% %entityId%" 5 | custom.item.linked="%itemName% (%itemId%) was linked to %entityType% %entityId%" 6 | custom.object.error.used.in.segments="Custom object "%name%" (#%id%) cannot be deleted because it's used in the following Segment(s): %segments%." 7 | custom.item.export.being.prepared="File is getting processed. The file will be send to your email address '%user_email%' You can download the file from the link provided in the email." 8 | custom.item.notice.merged="An item with the same unique identifier values already exists and the one you just inserted was merged with the existing one." -------------------------------------------------------------------------------- /Translations/en_US/validators.ini: -------------------------------------------------------------------------------- 1 | custom.item.choose.notblank="Some item must be provided. Type first few characters of the item name to see the options you can select from" 2 | custom.field.required="Field %fieldName% is required. Fill in some value." 3 | custom.field.option.invalid="Value '%value%' does not exist in the list of options of field '%fieldLabel%' (%fieldAlias%). Possible values: %possibleValues%" 4 | custom.field.url.invalid="'%value%' is not a valid URL address. Maybe you forgot to add the protocol like https://?" 5 | custom.field.phone.invalid="'%value%' is not a valid phone number. Use the following international phone number format [+][country code][subscriber number] for this field (eg: ‪+14028650000)" 6 | custom.field.email.invalid="'%value%' is not a valid email address." 7 | custom.field.allow.unique_identifier.invalid="System cannot modify unique identifier for the custom object which is in use" 8 | -------------------------------------------------------------------------------- /Views/CustomField/detail.html.php: -------------------------------------------------------------------------------- 1 | extend('MauticCoreBundle:Default:content.html.php'); 4 | $view['slots']->set('mauticContent', 'customField'); 5 | $view['slots']->set('headerTitle', $item->getName()); 6 | $view['slots']->set('actions', $view->render('MauticCoreBundle:Helper:page_actions.html.php', [ 7 | 'item' => $item, 8 | ])); 9 | ?> 10 | 11 | 12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 |
20 |
getType(); ?>
21 |
22 |
23 | render('MauticCoreBundle:Helper:publishstatus_badge.html.php', ['entity' => $item]); ?> 24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | 37 | 38 |
39 | 40 |
41 |
42 |
trans('mautic.webhook.webhook_url');?>
43 |
44 |
45 |
46 |
47 |
48 | 49 |
50 | 51 | 52 | render('MauticCoreBundle:Helper:recentactivity.html.php', ['logs' => $logs]);?> 53 |
54 |
55 | 56 |
57 | -------------------------------------------------------------------------------- /Views/CustomField/index.html.php: -------------------------------------------------------------------------------- 1 | extend('MauticCoreBundle:Default:content.html.php'); 4 | 5 | $view['slots']->set('mauticContent', 'customField'); 6 | $view['slots']->set('headerTitle', $view['translator']->trans('custom.field.title')); 7 | $view['slots']->set('actions', $view->render('MauticCoreBundle:Helper:page_actions.html.php')); 8 | ?> 9 | 10 |
11 | render( 12 | 'MauticCoreBundle:Helper:list_toolbar.html.php', 13 | [ 14 | // 'searchValue' => $searchValue, 15 | // 'action' => $currentRoute, 16 | ] 17 | ); ?> 18 |
19 | output('_content'); ?> 20 |
21 |
22 | -------------------------------------------------------------------------------- /Views/CustomField/value.html.php: -------------------------------------------------------------------------------- 1 | getCustomField()->getType(); 10 | ?> 11 | getValue() instanceof DateTimeInterface) : ?> 12 | 13 | toDate($fieldValue->getValue()); ?> 14 | 15 | toFull($fieldValue->getValue()); ?> 16 | 17 | 18 | escape($fieldValue->getCustomField()->getTypeObject()->valueToString($fieldValue)); ?> 19 | getValue())) : ?> 20 | escape($view['formatter']->arrayToString($fieldValue->getValue())); ?> 21 | 22 | escape($fieldValue->getValue()); ?> 23 | -------------------------------------------------------------------------------- /Views/CustomItem/index.html.php: -------------------------------------------------------------------------------- 1 | extend('MauticCoreBundle:Default:content.html.php'); 5 | 6 | $view['slots']->set('mauticContent', 'customItem'); 7 | $view['slots']->set('headerTitle', $customObject->getName()); 8 | $view['slots']->set('actions', $view->render('MauticCoreBundle:Helper:page_actions.html.php')); 9 | } 10 | ?> 11 | 12 |
13 |
14 | render( 15 | 'MauticCoreBundle:Helper:list_toolbar.html.php', 16 | [ 17 | 'searchValue' => $searchValue, 18 | 'action' => $currentRoute, 19 | 'target' => '#'.$namespace, 20 | 'overlayDisabled' => $lookup, 21 | ] 22 | ); ?> 23 |
24 | output('_content'); ?> 25 |
26 |
27 |
28 | -------------------------------------------------------------------------------- /Views/CustomObject/Form/Panel/checkbox_group.html.php: -------------------------------------------------------------------------------- 1 | extend('CustomObjectsBundle:CustomObject:Form\\Panel\\_field.html.php'); 6 | -------------------------------------------------------------------------------- /Views/CustomObject/Form/Panel/country.html.php: -------------------------------------------------------------------------------- 1 | extend('CustomObjectsBundle:CustomObject:Form\\Panel\\_field.html.php'); 6 | -------------------------------------------------------------------------------- /Views/CustomObject/Form/Panel/date.html.php: -------------------------------------------------------------------------------- 1 | extend('CustomObjectsBundle:CustomObject:Form\\Panel\\_field.html.php'); 6 | -------------------------------------------------------------------------------- /Views/CustomObject/Form/Panel/datetime.html.php: -------------------------------------------------------------------------------- 1 | extend('CustomObjectsBundle:CustomObject:Form\\Panel\\_field.html.php'); 6 | -------------------------------------------------------------------------------- /Views/CustomObject/Form/Panel/email.html.php: -------------------------------------------------------------------------------- 1 | extend('CustomObjectsBundle:CustomObject:Form\\Panel\\_field.html.php'); 6 | -------------------------------------------------------------------------------- /Views/CustomObject/Form/Panel/hidden.html.php: -------------------------------------------------------------------------------- 1 | extend('CustomObjectsBundle:CustomObject:Form\\Panel\\_field.html.php'); 6 | -------------------------------------------------------------------------------- /Views/CustomObject/Form/Panel/html_area.html.php: -------------------------------------------------------------------------------- 1 | extend('CustomObjectsBundle:CustomObject:Form\\Panel\\_field.html.php'); 6 | -------------------------------------------------------------------------------- /Views/CustomObject/Form/Panel/int.html.php: -------------------------------------------------------------------------------- 1 | extend('CustomObjectsBundle:CustomObject:Form\\Panel\\_field.html.php'); 6 | -------------------------------------------------------------------------------- /Views/CustomObject/Form/Panel/multiselect.html.php: -------------------------------------------------------------------------------- 1 | extend('CustomObjectsBundle:CustomObject:Form\\Panel\\_field.html.php'); 6 | -------------------------------------------------------------------------------- /Views/CustomObject/Form/Panel/phone.html.php: -------------------------------------------------------------------------------- 1 | extend('CustomObjectsBundle:CustomObject:Form\\Panel\\_field.html.php'); 6 | -------------------------------------------------------------------------------- /Views/CustomObject/Form/Panel/radio_group.html.php: -------------------------------------------------------------------------------- 1 | extend('CustomObjectsBundle:CustomObject:Form\\Panel\\_field.html.php'); 6 | -------------------------------------------------------------------------------- /Views/CustomObject/Form/Panel/select.html.php: -------------------------------------------------------------------------------- 1 | extend('CustomObjectsBundle:CustomObject:Form\\Panel\\_field.html.php'); 6 | -------------------------------------------------------------------------------- /Views/CustomObject/Form/Panel/text.html.php: -------------------------------------------------------------------------------- 1 | extend('CustomObjectsBundle:CustomObject:Form\\Panel\\_field.html.php'); 6 | -------------------------------------------------------------------------------- /Views/CustomObject/Form/Panel/textarea.html.php: -------------------------------------------------------------------------------- 1 | extend('CustomObjectsBundle:CustomObject:Form\\Panel\\_field.html.php'); 6 | -------------------------------------------------------------------------------- /Views/CustomObject/Form/Panel/url.html.php: -------------------------------------------------------------------------------- 1 | extend('CustomObjectsBundle:CustomObject:Form\\Panel\\_field.html.php'); 6 | -------------------------------------------------------------------------------- /Views/CustomObject/_form-fields.html.php: -------------------------------------------------------------------------------- 1 | children['customFields']->getIterator() as $customField): 12 | $customFieldEntity = $customField->vars['data']; 13 | if (!in_array($customFieldEntity->getId(), $deletedFields, true)) : 14 | echo $view->render( 15 | "CustomObjectsBundle:CustomObject:Form\\Panel\\{$customFieldEntity->getType()}.html.php", 16 | [ 17 | 'customField' => $customField, 18 | 'customObject' => $customObject, 19 | 'panelId' => $panelId ?? null, 20 | ] 21 | ); 22 | endif; 23 | endforeach; 24 | $form->children['customFields']->setRendered(); 25 | -------------------------------------------------------------------------------- /Views/CustomObject/index.html.php: -------------------------------------------------------------------------------- 1 | extend('MauticCoreBundle:Default:content.html.php'); 4 | 5 | $view['slots']->set('mauticContent', 'customObject'); 6 | $view['slots']->set('headerTitle', $view['translator']->trans('custom.object.title')); 7 | $view['slots']->set('actions', $view->render('MauticCoreBundle:Helper:page_actions.html.php')); 8 | ?> 9 | 10 |
11 | render( 12 | 'MauticCoreBundle:Helper:list_toolbar.html.php', 13 | [ 14 | // 'searchValue' => $searchValue, 15 | // 'action' => $currentRoute, 16 | ] 17 | ); ?> 18 |
19 | output('_content'); ?> 20 |
21 |
22 | -------------------------------------------------------------------------------- /Views/FormTheme/FieldValueCondition/campaign_condition_field_value_widget.html.php: -------------------------------------------------------------------------------- 1 | 4 |
5 |
6 | row($form['field']); ?> 7 |
8 |
9 | row($form['operator']); ?> 10 |
11 |
12 | row($form['value']); ?> 13 |
14 |
15 | 16 | -------------------------------------------------------------------------------- /Views/SubscribedEvents/Tab/content.html.php: -------------------------------------------------------------------------------- 1 | 6 |
7 |
8 |
9 | render( 10 | 'MauticCoreBundle:Helper:search.html.php', 11 | [ 12 | 'searchValue' => $searchValue, 13 | 'action' => $searchRoute, 14 | 'searchId' => $searchId, 15 | 'target' => '#'.$namespace, 16 | 'searchHelp' => '', 17 | ] 18 | ); ?> 19 |
20 | 38 |
39 |
40 | Loading... 41 |
42 |
43 | -------------------------------------------------------------------------------- /Views/SubscribedEvents/Tab/link.html.php: -------------------------------------------------------------------------------- 1 | 6 |
  • 7 | 8 | 9 | 10 | 11 | 12 | 13 | escape($view['translator']->trans($title)); ?> 14 | 15 |
  • -------------------------------------------------------------------------------- /Views/SubscribedEvents/Tab/modal.html.php: -------------------------------------------------------------------------------- 1 | append('modal', $view->render('MauticCoreBundle:Helper:modal.html.php', [ 6 | 'id' => 'customItemLookupModal', 7 | 'size' => 'xl', 8 | ])); 9 | -------------------------------------------------------------------------------- /Views/SubscribedEvents/Timeline/link.html.php: -------------------------------------------------------------------------------- 1 | 6 |
    7 | 8 |
    9 | trans('mautic.core.createdby'); ?> 10 |
    11 |
    12 | 13 | 14 | 15 |
    16 | 17 |
    18 | -------------------------------------------------------------------------------- /phpstan-baseline-php-versions.neon.php: -------------------------------------------------------------------------------- 1 | = 80000) { 8 | $includes[] = __DIR__.'/phpstan-baseline-8.0.neon'; 9 | } 10 | 11 | if (PHP_VERSION_ID >= 70400) { 12 | $includes[] = __DIR__.'/phpstan-baseline-7.4.neon'; 13 | } 14 | 15 | $config = []; 16 | $config['includes'] = $includes; 17 | $config['parameters']['phpVersion'] = PHP_VERSION_ID; 18 | 19 | return $config; 20 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - phpstan-baseline-php-versions.neon.php 3 | - ../../vendor/phpstan/phpstan/conf/bleedingEdge.neon 4 | 5 | parameters: 6 | level: 6 7 | reportUnmatchedIgnoredErrors: false 8 | checkGenericClassInNonGenericObjectType: false 9 | parallel: 10 | maximumNumberOfProcesses: 4 11 | processTimeout: 1000.0 12 | scanDirectories: 13 | - ../../app 14 | paths: 15 | - . 16 | excludePaths: 17 | - CustomFieldType/*Type.php # This should be refactored 18 | - *.html.php # this can be removed in Mautic 5 19 | - Extension/CustomItemListeningExtension.php # PHPSTAN is confused because it exists only if API Platform does. 20 | - Serializer/ApiNormalizer.php # PHPSTAN is confused because it exists only if API Platform does. 21 | - Tests/Unit/Serializer/ApiNormalizerTest.php # PHPSTAN is confused because it exists only if API Platform does. 22 | - DataPersister/CustomItemDataPersister.php # PHPSTAN is confused because it exists only if API Platform does. 23 | - Tests/Unit/EventListener/SegmentFiltersChoicesGenerateSubscriberTest.php 24 | dynamicConstantNames: 25 | - MAUTIC_ENV 26 | - MAUTIC_TABLE_PREFIX 27 | - MAUTIC_VERSION 28 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | Tests/Unit 14 | 15 | 16 | Tests/Functional 17 | 18 | 19 | Tests/Unit 20 | Tests/Functional 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | * 36 | 37 | Assets 38 | Config 39 | Migrations 40 | Tests 41 | Translations 42 | vendor 43 | Views 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | --------------------------------------------------------------------------------