Skip to content

Commit ea455c8

Browse files
authored
Merge branch '5.2' into edit-entity-twice-5.x
2 parents f05a553 + 3e00c36 commit ea455c8

File tree

46 files changed

+608
-171
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+608
-171
lines changed

.github/workflows/transifex.yml

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,39 @@
11
name: Push translations to Transifex
22

33
on:
4-
pull_request:
5-
types: [closed]
4+
push:
5+
branches:
6+
- '[0-9].*'
67
paths:
78
- '**/Translations/en_US/*.ini'
9+
workflow_dispatch:
810

911
permissions:
1012
contents: read
1113

1214
jobs:
1315
push-to-transifex:
16+
# Run only in the 'mautic/mautic' repository and on the default branch (or manually triggered via UI)
17+
if: github.repository == 'mautic/mautic' && (github.ref_name == github.event.repository.default_branch || github.event_name == 'workflow_dispatch')
1418
name: Push translations to Transifex
1519
runs-on: ubuntu-latest
16-
if: github.event.pull_request.merged == true
1720

1821
steps:
19-
- uses: actions/checkout@v4
22+
- name: Checkout
23+
uses: actions/checkout@v4
2024

21-
- name: Setup PHP, with composer and extensions
25+
- name: Setup PHP with extensions
2226
uses: shivammathur/setup-php@v2
2327
with:
2428
php-version: 8.3
2529
extensions: mbstring, xml, ctype, iconv, intl, pdo_sqlite, mysql, pdo_mysql
2630

27-
- name: Install dependencies
31+
- name: Install Composer dependencies
2832
uses: "ramsey/composer-install@v3"
2933

3034
- name: Push translations to Transifex
3135
env:
32-
MAUTIC_CONFIG_PARAMETERS: '{"transifex_api_token": "${{ secrets.TRANSIFEX_API_TOKEN }}" }'
36+
MAUTIC_CONFIG_PARAMETERS: '{"transifex_api_token": "${{ secrets.TRANSIFEX_API_TOKEN }}"}'
3337
run: |
3438
echo "MAUTIC_CONFIG_PARAMETERS=${MAUTIC_CONFIG_PARAMETERS}" >> $GITHUB_ENV
3539
php bin/console mautic:transifex:push

.htaccess

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@
9999

100100
# Apache 2.4+
101101
<IfModule authz_core_module>
102+
# Deny access via HTTP requests to all .env files.
103+
<FilesMatch "^\.env.*$">
104+
Require all denied
105+
</FilesMatch>
106+
102107
# Deny access via HTTP requests to all PHP files.
103108
<FilesMatch "\.php$">
104109
Require all denied
@@ -117,6 +122,12 @@
117122

118123
# Fallback for Apache < 2.4
119124
<IfModule !authz_core_module>
125+
# Deny access via HTTP requests to all .env files.
126+
<FilesMatch "^\.env.*$">
127+
Order deny,allow
128+
Deny from all
129+
</FilesMatch>
130+
120131
# Deny access via HTTP requests to all PHP files.
121132
<FilesMatch "\.php$">
122133
Order deny,allow

app/assets/scaffold/files/htaccess

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@
9999

100100
# Apache 2.4+
101101
<IfModule authz_core_module>
102+
# Deny access via HTTP requests to all .env files.
103+
<FilesMatch "^\.env.*$">
104+
Require all denied
105+
</FilesMatch>
106+
102107
# Deny access via HTTP requests to all PHP files.
103108
<FilesMatch "\.php$">
104109
Require all denied
@@ -117,6 +122,12 @@
117122

118123
# Fallback for Apache < 2.4
119124
<IfModule !authz_core_module>
125+
# Deny access via HTTP requests to all .env files.
126+
<FilesMatch "^\.env.*$">
127+
Order deny,allow
128+
Deny from all
129+
</FilesMatch>
130+
120131
# Deny access via HTTP requests to all PHP files.
121132
<FilesMatch "\.php$">
122133
Order deny,allow

app/bundles/AssetBundle/Entity/DownloadRepository.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,13 @@ public function getLeadDownloads($leadId = null, array $options = [])
5656
->leftJoin('d', MAUTIC_TABLE_PREFIX.'assets', 'a', 'd.asset_id = a.id');
5757

5858
if ($leadId) {
59-
$query->where('d.lead_id = '.(int) $leadId);
59+
$query->where('d.lead_id = :leadId')
60+
->setParameter('leadId', $leadId);
6061
}
6162

6263
if (isset($options['search']) && $options['search']) {
63-
$query->andWhere($query->expr()->like('a.title', $query->expr()->literal('%'.$options['search'].'%')));
64+
$query->andWhere('a.title LIKE :search')
65+
->setParameter('search', '%'.$options['search'].'%');
6466
}
6567

6668
return $this->getTimelineResults($query, $options, 'a.title', 'd.date_download', [], ['date_download'], null, 'd.id');

app/bundles/CampaignBundle/Entity/LeadEventLogRepository.php

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ public function getLeadLogs($leadId = null, array $options = [])
9595
->setParameter('eventType', 'decision');
9696

9797
if ($leadId) {
98-
$query->where('ll.lead_id = '.(int) $leadId);
98+
$query->where('ll.lead_id = :leadId')
99+
->setParameter('leadId', $leadId);
99100
}
100101

101102
if (isset($options['scheduledState'])) {
@@ -121,12 +122,12 @@ public function getLeadLogs($leadId = null, array $options = [])
121122
if (isset($options['search']) && $options['search']) {
122123
$query->andWhere(
123124
$query->expr()->or(
124-
$query->expr()->like('e.name', $query->expr()->literal('%'.$options['search'].'%')),
125-
$query->expr()->like('e.description', $query->expr()->literal('%'.$options['search'].'%')),
126-
$query->expr()->like('c.name', $query->expr()->literal('%'.$options['search'].'%')),
127-
$query->expr()->like('c.description', $query->expr()->literal('%'.$options['search'].'%'))
125+
$query->expr()->like('e.name', ':search'),
126+
$query->expr()->like('e.description', ':search'),
127+
$query->expr()->like('c.name', ':search'),
128+
$query->expr()->like('c.description', ':search')
128129
)
129-
);
130+
)->setParameter('search', '%'.$options['search'].'%');
130131
}
131132

132133
return $this->getTimelineResults($query, $options, 'e.name', 'll.date_triggered', ['metadata'], ['dateTriggered', 'triggerDate'], null, 'll.id');
@@ -363,30 +364,27 @@ public function getChartQuery($options): array
363364
->join('ll', MAUTIC_TABLE_PREFIX.'campaign_events', 'e', 'e.id = ll.event_id');
364365

365366
if (isset($options['channel'])) {
366-
$query->andwhere("e.channel = '".$options['channel']."'");
367+
$query->andWhere('e.channel = '.$query->expr()->literal($options['channel']));
367368
}
368369

369370
if (isset($options['channelId'])) {
370-
$query->andwhere('e.channel_id = '.(int) $options['channelId']);
371+
$query->andWhere('e.channel_id = '.(int) $options['channelId']);
371372
}
372373

373374
if (isset($options['type'])) {
374-
$query->andwhere("e.type = '".$options['type']."'");
375+
$query->andWhere('e.type = '.$query->expr()->literal($options['type']));
375376
}
376377

377378
if (isset($options['logChannel'])) {
378-
$query->andwhere("ll.channel = '".$options['logChannel']."'");
379+
$query->andWhere('ll.channel = '.$query->expr()->literal($options['logChannel']));
379380
}
380381

381382
if (isset($options['logChannelId'])) {
382-
$query->andwhere('ll.channel_id = '.(int) $options['logChannelId']);
383+
$query->andWhere('ll.channel_id = '.(int) $options['logChannelId']);
383384
}
384385

385-
if (!isset($options['is_scheduled'])) {
386-
$query->andWhere($query->expr()->eq('ll.is_scheduled', 0));
387-
} else {
388-
$query->andWhere($query->expr()->eq('ll.is_scheduled', 1));
389-
}
386+
$isScheduled = isset($options['is_scheduled']) ? 1 : 0;
387+
$query->andWhere('ll.is_scheduled = '.$isScheduled);
390388

391389
return $chartQuery->fetchTimeData('('.$query.')', 'date_triggered');
392390
}

app/bundles/CampaignBundle/Tests/Functional/Campaign/CampaignDecisionTest.php

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,17 @@
1515
use Mautic\LeadBundle\Entity\CompanyLead;
1616
use Mautic\LeadBundle\Entity\CompanyRepository;
1717
use Mautic\LeadBundle\Entity\Lead;
18-
use Mautic\LeadBundle\Entity\LeadField;
1918
use Mautic\LeadBundle\Entity\LeadList;
2019
use Mautic\LeadBundle\Entity\LeadRepository;
2120
use Mautic\LeadBundle\Entity\ListLead;
2221
use Mautic\LeadBundle\Model\CompanyModel;
23-
use Mautic\LeadBundle\Model\FieldModel;
2422
use Mautic\LeadBundle\Model\LeadModel;
23+
use Mautic\LeadBundle\Tests\Traits\LeadFieldTestTrait;
2524
use PHPUnit\Framework\Assert;
2625

2726
class CampaignDecisionTest extends MauticMysqlTestCase
2827
{
28+
use LeadFieldTestTrait;
2929
protected $useCleanupRollback = false;
3030

3131
/**
@@ -58,7 +58,7 @@ public function testCampaignContactFieldValueDecision(
5858
],
5959
],
6060
];
61-
$this->makeField($fieldDetails);
61+
$this->createField($fieldDetails);
6262

6363
$segment = $this->createSegment('seg1', []);
6464
$lead1 = $this->createLeadData($segment, $object, $fieldDetails, $additionalValue, 1);
@@ -164,24 +164,6 @@ private function getLeadIds(array $campaignEventLogs): array
164164
return $leadIds;
165165
}
166166

167-
/**
168-
* @param array<mixed> $fieldDetails
169-
*/
170-
private function makeField(array $fieldDetails): void
171-
{
172-
$field = new LeadField();
173-
$field->setLabel($fieldDetails['alias']);
174-
$field->setType($fieldDetails['type']);
175-
$field->setObject($fieldDetails['object'] ?? 'lead');
176-
$field->setGroup($fieldDetails['group'] ?? 'core');
177-
$field->setAlias($fieldDetails['alias']);
178-
$field->setProperties($fieldDetails['properties']);
179-
180-
$fieldModel = self::$container->get('mautic.lead.model.field');
181-
\assert($fieldModel instanceof FieldModel);
182-
$fieldModel->saveEntity($field);
183-
}
184-
185167
/**
186168
* @param array<mixed> $filters
187169
*

app/bundles/CategoryBundle/Controller/CategoryController.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,6 @@ public function newAction(Request $request, $bundle)
219219
$cancelled = $valid = false;
220220
$method = $request->getMethod();
221221
$inForm = $this->getInFormValue($request, $method);
222-
$showSelect = $request->get('show_bundle_select', false);
223222

224223
// not found
225224
if (!$this->security->isGranted($model->getPermissionBase($bundle).':create')) {
@@ -230,7 +229,7 @@ public function newAction(Request $request, $bundle)
230229
'objectAction' => 'new',
231230
'bundle' => $bundle,
232231
]);
233-
$form = $model->createForm($entity, $this->formFactory, $action, ['bundle' => $bundle, 'show_bundle_select' => $showSelect]);
232+
$form = $model->createForm($entity, $this->formFactory, $action, ['bundle' => $bundle, 'show_bundle_select' => 'category' === $bundle]);
234233
$form['inForm']->setData($inForm);
235234
// /Check for a submitted form and process it
236235
if (Request::METHOD_POST === $method) {

app/bundles/CategoryBundle/Tests/Controller/CategoryControllerFunctionalTest.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public function testNewActionWithInForm(): void
7878
$crawler->addHtmlContent($html);
7979
$saveButton = $crawler->selectButton('category_form[buttons][save]');
8080
$form = $saveButton->form();
81-
$form['category_form[bundle]']->setValue('category');
81+
$form['category_form[bundle]']->setValue('global');
8282
$form['category_form[title]']->setValue('Test');
8383
$form['category_form[isPublished]']->setValue('1');
8484
$form['category_form[inForm]']->setValue('1');
@@ -110,4 +110,24 @@ public function testEditLockCategory(): void
110110
$this->client->request(Request::METHOD_GET, 's/categories/category/edit/'.$category->getId());
111111
$this->assertStringContainsString('is currently checked out by', $this->client->getResponse()->getContent());
112112
}
113+
114+
public function testTypeFieldPersistsAfterValidationFailure(): void
115+
{
116+
$crawler = $this->client->request(Request::METHOD_GET, 's/categories/category/new');
117+
$clientResponse = json_decode($this->client->getResponse()->getContent(), true);
118+
$html = $clientResponse['newContent'];
119+
$crawler->addHtmlContent($html);
120+
$saveButton = $crawler->selectButton('category_form[buttons][save]');
121+
$form = $saveButton->form();
122+
$form['category_form[bundle]']->setValue('global');
123+
$form['category_form[title]']->setValue('');
124+
$form['category_form[isPublished]']->setValue('1');
125+
$form['category_form[inForm]']->setValue('1');
126+
127+
$this->client->submit($form);
128+
Assert::assertTrue($this->client->getResponse()->isOk());
129+
$clientResponse = $this->client->getResponse();
130+
$body = json_decode($clientResponse->getContent(), true);
131+
$this->assertNotEmpty($crawler->filter('select#category_form_bundle')->count(), 'The "Type" (bundle) field should remain visible after validation failure.');
132+
}
113133
}

app/bundles/ChannelBundle/Entity/MessageQueueRepository.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,14 @@ public function getLeadTimelineEvents($leadId = null, array $options = [])
102102
mq.scheduled_date as scheduledDate, mq.last_attempt as lastAttempt, mq.date_sent as dateSent');
103103

104104
if ($leadId) {
105-
$query->where('mq.lead_id = '.(int) $leadId);
105+
$query->where('mq.lead_id = :leadId')
106+
->setParameter('leadId', $leadId);
106107
}
107108

108109
if (isset($options['search']) && $options['search']) {
109-
$query->andWhere($query->expr()->or(
110-
$query->expr()->like('mq.channel', $query->expr()->literal('%'.$options['search'].'%'))
111-
));
110+
$query->andWhere(
111+
$query->expr()->like('mq.channel', ':search')
112+
)->setParameter('search', '%'.$options['search'].'%');
112113
}
113114

114115
return $this->getTimelineResults($query, $options, 'mq.channel', 'mq.date_published', [], ['dateAdded'], null, 'mq.id');

app/bundles/CoreBundle/Controller/AbstractFormController.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Symfony\Component\Form\Form;
77
use Symfony\Component\Form\FormError;
88
use Symfony\Component\Form\FormInterface;
9+
use Symfony\Component\HttpFoundation\RedirectResponse;
910
use Symfony\Component\HttpFoundation\Request;
1011

1112
abstract class AbstractFormController extends CommonController
@@ -25,15 +26,25 @@ public function unlockAction(Request $request, $objectId, $objectModel)
2526
if (null !== $entity && null !== $entity->getCheckedOutBy()) {
2627
$model->unlockEntity($entity);
2728
}
28-
$returnUrl = urldecode($request->get('returnUrl'));
29-
if (empty($returnUrl)) {
29+
30+
$currentUrl = $request->getUri();
31+
$returnUrl = urldecode($request->get('returnUrl'));
32+
33+
if (!filter_var($returnUrl, FILTER_VALIDATE_URL)) {
3034
$returnUrl = $this->generateUrl('mautic_dashboard_index');
35+
} else {
36+
$currentHost = parse_url($currentUrl, PHP_URL_HOST);
37+
$returnHost = parse_url($returnUrl, PHP_URL_HOST);
38+
39+
if ($currentHost !== $returnHost) {
40+
$returnUrl = $this->generateUrl('mautic_dashboard_index');
41+
}
3142
}
3243

3344
$this->addFlashMessage(
3445
'mautic.core.action.entity.unlocked',
3546
[
36-
'%name%' => urldecode($request->get('name')),
47+
'%name%' => htmlspecialchars(urldecode($request->get('name')), ENT_QUOTES, 'UTF-8'),
3748
]
3849
);
3950

@@ -51,7 +62,7 @@ public function unlockAction(Request $request, $objectId, $objectModel)
5162
* @param string $model
5263
* @param bool $batch Flag if a batch action is being performed
5364
*
54-
* @return \Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse|array
65+
* @return \Symfony\Component\HttpFoundation\JsonResponse|RedirectResponse|array
5566
*/
5667
protected function isLocked($postActionVars, $entity, $model, $batch = false)
5768
{

0 commit comments

Comments
 (0)