PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.1.0
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.1.0
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
← All changes | back/Classes/FieldClass.php +92 -34 11.0.2 → 11.1.0 View file →
@@ -142,10 +142,11 @@
142 142 }
143 143
144 144 public function store(int $userID, array $fields, bool $ajax = false): void
145 145 {
146 + $uploadedFileFields = [];
146 147 $customField = acym_getVar('array', 'customField', [], 'FILES');
147 - if (!empty($customField)) {
148 + if (!empty($customField['tmp_name'])) {
148 149 $uploadFolder = trim(acym_cleanPath(html_entity_decode(acym_getFilesFolder(true))), DS.' ').DS;
149 150 $uploadPath = acym_cleanPath(ACYM_ROOT.$uploadFolder.'userfiles'.DS.$userID.DS);
150 151 if (!file_exists($uploadPath)) {
151 152 acym_createDir($uploadPath);
@@ -160,10 +161,11 @@
160 161 $fileName = $customField['name'][$key];
161 162 while (is_array($fileName) && isset($fileName[0])) {
162 163 $fileName = $fileName[0];
163 164 }
165 + $fileName = basename($fileName);
164 166
165 - if (!preg_match('#\.('.implode('|', $allowedExtensions).')$#Ui', $fileName)) {
167 + if (!acym_fileNameValid($fileName) || !preg_match('#\.('.implode('|', array_map('preg_quote', $allowedExtensions)).')$#i', $fileName)) {
166 168 $ext = substr($fileName, strrpos($fileName, '.') + 1);
167 169 if ($ajax) {
168 170 $this->errors[] = acym_translationSprintf(
169 171 'ACYM_ACCEPTED_TYPE',
@@ -184,9 +186,9 @@
184 186
185 187 continue;
186 188 }
187 189
188 - if (!acym_uploadFile($value, $uploadPath.$fileName)) {
190 + if (!acym_isPathWithinDirectory($uploadPath, $uploadPath.$fileName) || !acym_uploadFile($value, $uploadPath.$fileName)) {
189 191 if ($ajax) {
190 192 $this->errors[] = acym_translation('ACYM_ERROR_SAVING');
191 193 } else {
192 194 acym_enqueueMessage(acym_translation('ACYM_ERROR_SAVING'), 'error');
@@ -193,9 +195,10 @@
193 195 }
194 196
195 197 continue;
196 198 }
197 - $fields[$key] = $customField['name'][$key];
199 + $fields[$key] = $fileName;
200 + $uploadedFileFields[$key] = true;
198 201 }
199 202 }
200 203
201 204 if (empty($fields)) return;
@@ -248,8 +251,13 @@
248 251 if (in_array($field->type, ['text', 'textarea']) && !empty($fieldOptions->max_characters)) {
249 252 $value = substr($value, 0, $fieldOptions->max_characters);
250 253 }
251 254
255 + // A file field value can only be set through an actual upload, never through a plain posted string
256 + if ($field->type === 'file' && strlen($value) !== 0 && empty($uploadedFileFields[$id])) {
257 + continue;
258 + }
259 +
252 260 // If deleting a file field, also delete the physical file
253 261 if ($field->type === 'file' && strlen($value) === 0) {
254 262 $oldValue = acym_loadResult(
255 263 'SELECT `value` FROM #__acym_user_has_field WHERE `user_id` = '.intval($userID).' AND `field_id` = '.intval($id)
@@ -258,15 +266,21 @@
258 266 $fileName = @json_decode($oldValue, true);
259 267 if (is_array($fileName)) $fileName = $fileName[0];
260 268 if (empty($fileName)) $fileName = $oldValue;
261 269
262 - $uploadFolder = trim(acym_cleanPath(html_entity_decode(acym_getFilesFolder(true))), DS.' ').DS;
263 - // Try new path (with user folder) first, then legacy path (without user folder)
264 - $filePath = acym_cleanPath(ACYM_ROOT.$uploadFolder.'userfiles'.DS.$userID.DS.$fileName);
265 - if (!file_exists($filePath)) {
266 - $filePath = acym_cleanPath(ACYM_ROOT.$uploadFolder.'userfiles'.DS.$fileName);
270 + $fileName = basename($fileName);
271 + if (acym_fileNameValid($fileName)) {
272 + $uploadFolder = trim(acym_cleanPath(html_entity_decode(acym_getFilesFolder(true))), DS.' ').DS;
273 + $userfilesBase = acym_cleanPath(ACYM_ROOT.$uploadFolder.'userfiles'.DS);
274 + // Try new path (with user folder) first, then legacy path (without user folder)
275 + $filePath = acym_cleanPath($userfilesBase.$userID.DS.$fileName);
276 + if (!file_exists($filePath)) {
277 + $filePath = acym_cleanPath($userfilesBase.$fileName);
278 + }
279 + if (acym_isPathWithinDirectory($userfilesBase, $filePath)) {
280 + acym_deleteFile($filePath);
281 + }
267 282 }
268 - acym_deleteFile($filePath);
269 283 }
270 284 }
271 285
272 286 // The user removed a value, don't add an empty line and remove any previous value from the bd
@@ -561,19 +575,45 @@
561 575 : json_encode($defaultValue)
562 576 );
563 577 }
564 578
565 - if (
566 - $displayOutside
567 - && (
568 - in_array($field->id, [1, 2])
569 - || in_array($field->type, ['text', 'textarea', 'single_dropdown', 'multiple_dropdown', 'custom_text', 'file', 'language'])
570 - )
571 - ) {
572 - echo '<label class="cell margin-top-1"><span class="acym__users__creation__fields__title">'.esc_html($field->name).'</span>';
579 + $allowedFieldNameTags = array_merge(SecurityHelper::ALLOWED_HTML_FIELD_NAME, [
580 + 'a' => [
581 + 'href' => true,
582 + 'target' => true,
583 + 'title' => true,
584 + 'class' => true,
585 + 'rel' => true,
586 + 'aria-label' => true,
587 + ],
588 + ]);
589 +
590 + $labelTypes = ['text', 'textarea', 'single_dropdown', 'multiple_dropdown', 'custom_text', 'language'];
591 + $titleTypes = ['date', 'radio', 'checkbox', 'file'];
592 +
593 + $useLabel = $displayOutside && (in_array($field->id, [1, 2]) || in_array($field->type, $labelTypes));
594 + $useTitle = $displayOutside && in_array($field->type, $titleTypes);
595 +
596 + if ($useLabel) {
597 + echo '<label class="cell margin-top-1"><span class="acym__users__creation__fields__title">'.wp_kses(
598 + $field->name,
599 + SecurityHelper::ALLOWED_HTML_FIELD_NAME
600 + ).'</span>';
573 601 }
574 - if ($displayOutside && in_array($field->type, ['date', 'radio', 'checkbox'])) {
575 - echo '<div class="cell margin-top-1"><div class="acym__users__creation__fields__title">'.esc_html($field->name).'</div>';
602 + if ($useTitle) {
603 + if (in_array($field->type, ['date', 'radio', 'checkbox'])) {
604 + $groupLabelId = 'acym__field__label__'.intval($field->id).'_'.acym_rand(1000, 9999);
605 + echo '<div class="cell margin-top-1" role="group" aria-labelledby="'.esc_attr($groupLabelId).'"'.($field->required ? ' aria-required="true"' : '').'>';
606 + echo '<div id="'.esc_attr($groupLabelId).'" class="acym__users__creation__fields__title">'.wp_kses(
607 + $field->name,
608 + $allowedFieldNameTags
609 + ).'</div>';
610 + } else {
611 + echo '<div class="cell margin-top-1"><div class="acym__users__creation__fields__title">'.wp_kses(
612 + $field->name,
613 + $allowedFieldNameTags
614 + ).'</div>';
615 + }
576 616 }
577 617
578 618 $messageRequired = empty($field->option->error_message) ? '' : acym_translation($field->option->error_message);
579 619 $requiredJson = json_encode(['type' => $field->type, 'message' => $messageRequired]);
@@ -579,10 +619,12 @@
579 619 $requiredJson = json_encode(['type' => $field->type, 'message' => $messageRequired]);
580 620 $required = '';
581 621 if ($field->required) {
582 622 $data['data-required'] = $requiredJson;
623 + $data['aria-required'] = 'true';
583 624 $required = ' data-required="'.esc_attr($requiredJson).'"';
584 625 $attributesSelectField['data-required'] = $requiredJson;
626 + $attributesSelectField['aria-required'] = 'true';
585 627 }
586 628
587 629 $readonly = '';
588 630 if (!$showField && acym_isAdmin() && acym_level(ACYM_ENTERPRISE) && $isCoreField) {
@@ -681,8 +723,11 @@
681 723 echo '<textarea name="'.esc_attr($name).'"';
682 724 if (!empty($data['data-required'])) {
683 725 echo ' data-required="'.esc_attr($data['data-required']).'"';
684 726 }
727 + if (!empty($data['aria-required'])) {
728 + echo ' aria-required="true"';
729 + }
685 730 if (isset($data['maxCharacters'])) {
686 731 echo ' maxlength="'.esc_attr($data['maxCharacters']).'"';
687 732 }
688 733 echo ' rows="'.intval($field->option->rows).'" cols="'.intval($field->option->columns).'">';
@@ -727,8 +772,9 @@
727 772 'style' => empty($field->option->size) ? '' : 'width:'.$field->option->size.'px',
728 773 ];
729 774 if ($field->required) {
730 775 $attributes['data-required'] = $displayFront ? esc_attr($requiredJson) : $requiredJson;
776 + $attributes['aria-required'] = 'true';
731 777 }
732 778
733 779 acym_selectMultiple(
734 780 $valuesArray,
@@ -760,8 +806,9 @@
760 806 [
761 807 'select' => [
762 808 'data-required' => true,
763 809 'acym-field-type' => true,
810 + 'aria-label' => true,
764 811 ],
765 812 'div' => [
766 813 'class' => true,
767 814 ],
@@ -777,9 +824,10 @@
777 824 } else {
778 825 $downloadUrl = '';
779 826 if (!empty($defaultValue) && !empty($user->id)) {
780 827 $fileName = is_array($defaultValue) ? $defaultValue[0] : $defaultValue;
781 - if (!empty($fileName)) {
828 + $fileName = basename($fileName);
829 + if (!empty($fileName) && acym_fileNameValid($fileName)) {
782 830 $uploadFolder = trim(acym_cleanPath(html_entity_decode(acym_getFilesFolder(true))), DS.' ').DS;
783 831 $userFilePath = $uploadFolder.'userfiles'.DS.$user->id.DS.$fileName;
784 832 $legacyFilePath = $uploadFolder.'userfiles'.DS.$fileName;
785 833
@@ -800,9 +848,14 @@
800 848 $indicator = !empty($defaultValue[0]) ? $defaultValue[0] : '';
801 849 $number = !empty($defaultValue[1]) ? $defaultValue[1] : '';
802 850
803 851 if ($displayOutside) {
804 - echo '<div class="cell margin-top-1 grid-x"><div class="acym__users__creation__fields__title cell">'.esc_html($field->name).'</div>';
852 + $phoneLabelId = 'acym__field__label__'.intval($field->id).'_'.acym_rand(1000, 9999);
853 + echo '<div class="cell margin-top-1 grid-x" role="group" aria-labelledby="'.esc_attr($phoneLabelId).'"'.($field->required ? ' aria-required="true"' : '').'>';
854 + echo '<div id="'.esc_attr($phoneLabelId).'" class="acym__users__creation__fields__title cell">'.wp_kses(
855 + $field->name,
856 + SecurityHelper::ALLOWED_HTML_FIELD_NAME
857 + ).'</div>';
805 858 }
806 859 echo '<div class="cell large-5 medium-4 padding-next-1">';
807 860 acym_generateCountryNumber($name.'[code]', $indicator);
808 861 echo '</div>';
@@ -812,8 +865,11 @@
812 865 $data['name'] = $name.'[phone]';
813 866 $data['autocomplete'] = 'tel-national';
814 867 $data['class'] = 'cell large-7 medium-8';
815 868 $data['value'] = $number;
869 + if ($displayOutside) {
870 + $data['aria-label'] = acym_translation('ACYM_PHONE_NUMBER');
871 + }
816 872 include acym_getPartial('fields', 'input');
817 873
818 874 if ($displayOutside) {
819 875 echo '</div>';
@@ -822,17 +878,16 @@
822 878 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Admin-defined custom HTML content, intentionally rendered as-is.
823 879 echo $field->option->custom_text;
824 880 }
825 881
826 - $labelTypes = ['text', 'textarea', 'single_dropdown', 'multiple_dropdown', 'custom_text', 'language'];
827 - if ($displayOutside && (in_array($field->id, [1, 2]) || in_array($field->type, $labelTypes))) {
882 + if ($useLabel) {
828 883 echo '</label>';
829 884 }
830 - if ($displayOutside && in_array($field->type, ['date', 'radio', 'checkbox'])) {
885 + if ($useTitle) {
831 886 echo '</div>';
832 887 }
833 888
834 - echo '<div class="acym__field__error__block" data-acym-field-id="'.intval($field->id).'"></div>';
889 + echo '<div class="acym__field__error__block" data-acym-field-id="'.intval($field->id).'" aria-live="assertive"></div>';
835 890 if ($displayFront && $extraErrors && !acym_isAdmin()) {
836 891 echo '<div class="acym__message__invalid__field acym__color__error" style="display: none;">';
837 892 echo '<i class="acymicon-times-circle acym__cross__invalid acym__color__error"></i>'.esc_html(acym_translation('ACYM_THANKS_TO_FILL_IN_THIS_FIELD')).'</div>';
838 893 }
@@ -984,16 +1039,19 @@
984 1039 echo '<span class="acym__users__creation__fields__title">'.esc_html(acym_translation('ACYM_EMAIL_CONFIRMATION')).'</span>';
985 1040 }
986 1041
987 1042 $placeholder = !$displayOutside ? acym_translation('ACYM_EMAIL_CONFIRMATION') : '';
988 - echo '<input id="'.esc_attr($uniqueId).'"
989 - '.(empty($field->option->size) ? '' : ' style="width:'.intval($field->option->size).'px"').'
990 - required
991 - type="email"
992 - class="cell acym__user__edit__email"
993 - name="user[email_confirmation]"
994 - placeholder="'.esc_attr($placeholder).'" />';
995 - echo '<span class="acym__field__error__block"></span>';
1043 + echo '<input id="'.esc_attr($uniqueId).'"
1044 + '.(empty($field->option->size) ? '' : ' style="width:'.intval($field->option->size).'px"').'
1045 + required
1046 + type="email"
1047 + class="cell acym__user__edit__email"
1048 + name="user[email_confirmation]"';
1049 + if (!$displayOutside) {
1050 + echo ' aria-label="'.esc_attr(acym_translation('ACYM_EMAIL_CONFIRMATION')).'"';
1051 + }
1052 + echo ' placeholder="'.esc_attr($placeholder).'" />';
1053 + echo '<span class="acym__field__error__block" aria-live="assertive"></span>';
996 1054
997 1055 $extraErrors = $this->config->get('extra_errors', '0');
998 1056 if ($extraErrors && !acym_isAdmin()) {
999 1057 echo '<span class="acym__message__invalid__field acym__color__error" style="display: none;">';