PluginProbe
wpForo Forum / 2.3.0
wpForo Forum v2.3.0
3.1.5 3.1.4 3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 1.4.11 1.4.12 1.4.13 1.4.2 All 137 releases
wpforo / classes / Forms.php

Forms.php in wpForo Forum 2.3.0, at classes/Forms.php

1,585 lines 62.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace wpforo\classes;
4
5 // Exit if accessed directly
6 if( ! defined( 'ABSPATH' ) ) exit;
7
8 class Forms {
9 public $default;
10 public $field_dir;
11 public $current;
12
13 public function __construct() {
14 $this->init_defaults();
15 }
16
17 private function init_defaults() {
18 $this->field_dir = '/wpforo/users/fields/';
19 $this->default = [
20 'label' => '',
21 'title' => '',
22 'name' => '',
23 'value' => '',
24 'values' => '',
25 'type' => 'text',
26 'placeholder' => '',
27 'description' => '',
28 'id' => '',
29 'class' => '',
30 'attributes' => '',
31 'isDefault' => 0,
32 'isWrapItem' => '',
33 'isLabelFirst' => '',
34 'isDisabled' => 0,
35 'isEditable' => 1,
36 'isRequired' => 0,
37 'isMultiChoice' => 0,
38 'isAllowedCustomValues' => 0,
39 'UGroupIdsFrontAddNewDefaultValues' => [],
40 'isOnlyForGuests' => 0,
41 'isRemovable' => 1,
42 'isSearchable' => 0,
43 'allowedGroupIds' => [],
44 'fileExtensions' => '',
45 'fileSize' => 1,
46 'minLength' => 0,
47 'maxLength' => 0,
48 'faIcon' => '',
49 'html' => '',
50 'varname' => '',
51 'template' => '',
52 'cantBeInactive' => [],
53 'canEdit' => [ 1 ],
54 'canView' => [ 1, 2, 3, 5 ],
55 'can' => '',
56 'isDisplayDefaultValues' => 0,
57 ];
58 }
59
60 public function fix_field( $field ) {
61 return wpforo_array_args_cast_and_merge( (array) $field, $this->default );
62 }
63
64 /**
65 * Form builder
66 * for form layout and field building
67 *
68 * @param array $fields associative multi-level array
69 *
70 * @return string form HTML
71 */
72 public function build( $fields ) {
73 if( empty( $fields ) ) return '';
74 $html = '';
75 foreach( $fields as $row_key => $row ) {
76 $row_class = "row-$row_key " . apply_filters( 'wpforo_row_classes', '', $row_key );
77 $html .= '<div class="wpf-tr ' . esc_attr( $row_class ) . '">';
78 foreach( $row as $col_key => $col ) {
79 $col_class_field = '';
80 foreach( $col as $field ) {
81 $col_class_field .= ' wpf-row-' . wpfval( $field, 'name' );
82 }
83 reset( $col );
84 $col_class = "row_$row_key-col_$col_key " . $col_class_field . ' ' . apply_filters( 'wpforo_col_classes', '', $row_key, $col_key );
85 $html .= '<div class="wpf-td wpfw-' . count( $row ) . ' ' . esc_attr( $col_class ) . '">';
86 foreach( $col as $field ) {
87 if( ! empty( $field ) ) $html .= $this->build_field( $field );
88 }
89 $html .= '</div>';
90 }
91 $html .= '<div class="wpf-cl"></div></div>';
92 }
93
94 return $html;
95 }
96
97 /**
98 * Builds field with input HTML and wrapper divs
99 *
100 * @param array $args field arguments
101 *
102 * @return string field HTML
103 */
104 public function build_field( $args ) {
105 if( is_string( $args ) ) $args = WPF()->member->get_field( $args );
106
107 $html = '';
108 if( ! is_array( $args ) || empty( $args ) ) return '';
109 $f = wpforo_parse_args( $args, $this->default );
110 $f = $this->prepare_args( $f );
111
112 //Get field input tag
113 $field_html = $this->field( $f );
114
115 //Wrapping field input
116 if( $f['template'] === 'register' ) {
117 if( $this->can_add( $f ) ) {
118 $html = $this->field_wrap_register( $field_html, $f );
119 }
120 } elseif( $f['template'] === 'account' ) {
121 if( $this->can_edit( $f ) ) {
122 $html = $this->field_wrap_account( $field_html, $f );
123 }
124 } elseif( $f['template'] === 'profile' ) {
125 if( $this->can_view( $f ) && $this->can_show_value( $f ) ) {
126 $f = $this->esc_field( $f );
127 $html = $this->field_wrap_profile( $field_html, $f );
128 }
129 } elseif( $f['template'] === 'members' ) {
130 if( $this->can_show_value( $f ) ) $html = $this->field_wrap_members( $field_html, $f );
131 } else {
132 $html = $this->field_wrap_default( $field_html, $f );
133 }
134
135 return $html;
136 }
137
138 /**
139 * Field input HTML builder
140 *
141 * @param array $f field arguments
142 *
143 * @return string field input HTML
144 */
145 public function field( $f ) {
146 $f = $this->prepare_field_args( $f );
147
148 $f = apply_filters( 'wpforo_form_field', $f );
149
150 switch( $f['type'] ) {
151 case 'url':
152 $field_html = $this->field_url( $f );
153 break;
154 case 'file':
155 $field_html = $this->field_file( $f );
156 break;
157 case 'html':
158 $field_html = $this->field_html( $f );
159 break;
160 case 'radio':
161 $field_html = $this->field_radio( $f );
162 break;
163 case 'select':
164 $field_html = $this->field_select( $f );
165 break;
166 case 'avatar':
167 $field_html = $this->field_avatar( $f );
168 break;
169 case 'textarea':
170 $field_html = $this->field_textarea( $f );
171 break;
172 case 'tinymce':
173 $field_html = $this->field_tinymce( $f );
174 break;
175 case 'password':
176 $field_html = $this->field_password( $f );
177 break;
178 case 'checkbox':
179 $field_html = $this->field_checkbox( $f );
180 break;
181 case 'usergroup':
182 $field_html = $this->field_usergroup( $f );
183 break;
184 case 'secondary_groups':
185 $field_html = $this->field_secondary_groups( $f );
186 break;
187 case 'user_nicename':
188 $field_html = $this->field_nicename( $f );
189 break;
190 case 'tel':
191 $field_html = $this->field_tel( $f );
192 break;
193 case 'autocomplete':
194 $field_html = $this->field_autocomplete( $f );
195 break;
196 default:
197 $field_html = $this->field_text( $f );
198 }
199
200 return apply_filters( 'wpforo_form_field_html', $field_html, $f );
201 }
202
203 /**
204 * Wraps input HTML for Registration form
205 *
206 * @param string $field_html input HTML
207 * @param array $f field arguments
208 *
209 * @return string wrapped field input HTML
210 */
211 public function field_wrap_register( $field_html, $f ) {
212 $html = '<div class="wpf-field wpf-field-type-' . esc_attr( $f['type'] ) . ' wpf-field-name-' . esc_attr( $f['field_class'] ) . ' ' . esc_attr( $f['required_class'] ) . '" title="' . esc_attr( $f['title'] ) . '">';
213 if( $f['type'] !== 'html' ) {
214 if( $f['label'] || $f['description'] ) {
215 $html .= '<div class="wpf-label-wrap">';
216 if( $f['label'] ) {
217 $html .= '<p class="wpf-label wpfcl-1">' . $f['label'] . $f['required_indicator'] . '</p>';
218 }
219 if( $f['description'] ) {
220 $html .= '<div class="wpf-desc wpfcl-2">' . $f['description'] . '</div>';
221 }
222 $html .= '</div>';
223 }
224 $html .= '<div class="wpf-field-wrap">';
225 if( $f['faIcon'] ) {
226 $html .= '<i class="' . esc_attr( $f['faIcon'] ) . ' wpf-field-icon"></i>';
227 }
228 $html .= $field_html;
229 $html .= '</div>';
230 } else {
231 $html .= $field_html;
232 }
233 $html .= '<div class="wpf-field-cl"></div></div>';
234
235 return $html;
236 }
237
238 /**
239 * Wraps input HTML for Account form
240 *
241 * @param string $field_html input HTML
242 * @param array $f field arguments
243 *
244 * @return string wrapped field input HTML
245 */
246 public function field_wrap_account( $field_html, $f ) {
247 $html = '<div class="wpf-field wpf-field-type-' . esc_attr( $f['type'] ) . ' wpf-field-name-' . esc_attr( $f['field_class'] ) . ' ' . esc_attr( $f['required_class'] ) . '" title="' . esc_attr( $f['title'] ) . '">';
248 if( $f['type'] === 'html' ) {
249 $html .= $field_html;
250 } elseif( $f['name'] === 'user_login' ) {
251 $html .= '<div class="wpf-label-wrap">';
252 $html .= '<p class="wpf-label wpfcl-1">' . stripslashes( (string) $f['label'] ) . '</p>';
253 $html .= '</div>';
254 $html .= '<div class="wpf-field-wrap">';
255 $html .= '<span class="wpf-username">' . $f['value'] . '</span>';
256 $html .= '</div>';
257 } elseif( $f['type'] !== 'password' && ! $f['isEditable'] && ! $this->can_moderate( $f ) && WPF()->current_user_groupid !== 1 ) {
258 $f = $this->esc_field( $f );
259 $html .= '<div class="wpf-label-wrap">';
260 $html .= '<p class="wpf-label wpfcl-1">' . stripslashes( (string) $f['label'] ) . '</p>';
261 $html .= '</div>';
262 $html .= '<div class="wpf-field-wrap">';
263 $html .= '<span class="wpf-filed-value"><i class="' . esc_attr( $f['faIcon'] ) . '"></i> ' . $f['value'] . '</span>';
264 $html .= '</div>';
265 } else {
266 if( $f['label'] || $f['description'] ) {
267 $html .= '<div class="wpf-label-wrap">';
268 if( $f['label'] ) {
269 $html .= '<p class="wpf-label wpfcl-1">' . stripslashes( (string) $f['label'] ) . $f['required_indicator'] . '</p>';
270 }
271 if( $f['description'] ) {
272 $html .= '<div class="wpf-desc wpfcl-2">' . $f['description'] . '</div>';
273 }
274 $html .= '</div>';
275 }
276 $html .= '<div class="wpf-field-wrap">';
277 if( $f['faIcon'] ) {
278 $html .= '<i class="' . esc_attr( $f['faIcon'] ) . ' wpf-field-icon"></i>';
279 }
280 $html .= $field_html;
281 $html .= '</div>';
282 }
283 $html .= '<div class="wpf-field-cl"></div></div>';
284
285 return $html;
286 }
287
288 /**
289 * Wraps input HTML for Profile page
290 *
291 * @param string $field_html input HTML
292 * @param array $f field arguments
293 *
294 * @return string wrapped field input HTML
295 */
296 public function field_wrap_profile( $field_html, $f ) {
297
298 if( ! in_array( $f['type'], [ 'html', 'avatar' ] ) && ! ( $f['isRequired'] && $f['isDisplayDefaultValues'] ) && ( ! isset( $f['value'] ) || ( ! is_numeric( $f['value'] ) && empty( $f['value'] ) ) ) ) {
299 return false;
300 }
301
302 $html = '<div class="wpf-field wpf-field-type-' . esc_attr( $f['type'] ) . ' wpf-field-name-' . esc_attr( $f['field_class'] ) . ' ' . esc_attr( $f['required_class'] ) . '" title="' . esc_attr( $f['title'] ) . '">';
303 if( $f['type'] !== 'html' ) {
304 if( ! $f['faIcon'] ) {
305 $f['faIcon'] = 'fas fa-address-card';
306 }
307 if( $f['label'] ) {
308 $html .= '<div class="wpf-label-wrap"><p class="wpf-label wpfcl-1"><i class="' . esc_attr( $f['faIcon'] ) . ' wpf-field-icon"></i> ' . $f['label'] . '</p></div>';
309 }
310 if( $f['type'] === 'avatar' || ( isset( $f['value'] ) && ! empty( $f['value'] ) ) ) {
311 if( is_array( $f['value'] ) ) {
312 $html .= esc_html( implode( ', ', $f['value'] ) );
313 } else {
314 $f = $this->prepare_values( $f, WPF()->current_object['userid'] );
315 $html .= '<div class="wpf-field-wrap">';
316 $html .= $f['value'];
317 $html .= '</div>';
318 }
319 } else {
320 if( $default_values = wpforo_preg_grep_recursive( '#^\[.+?]$#isu', $f['values'] ) ) {
321 $_html = '';
322 foreach( $default_values as $default_key => $default_value ) {
323 if( ! is_array( $default_value ) ) {
324 $item = $this->build_item_value_lable( $default_value );
325 $_html .= $item['label'] . ', ';
326 } else {
327 $csv = '';
328 foreach( $default_value as $_default_value ) {
329 if( ! is_array( $_default_value ) ) {
330 $item = $this->build_item_value_lable( $_default_value );
331 $csv .= $item['label'] . ', ';
332 }
333 }
334 $_html .= trim( (string) $default_key ) . '( ' . trim( (string) $csv, ', ' ) . ' ), ';
335 }
336 }
337 $html .= sprintf( '<div class="wpf-field-wrap">%1$s</div>', trim( (string) $_html, ', ' ) );
338 } else {
339 return false;
340 }
341 }
342 } else {
343 $html .= $field_html;
344 }
345 $html .= '<div class="wpf-field-cl"></div></div>';
346
347 return $html;
348 }
349
350 /**
351 * Wraps input HTML for Members Search form
352 *
353 * @param string $field_html input HTML
354 * @param array $f field arguments
355 *
356 * @return string wrapped field input HTML
357 */
358 public function field_wrap_members( $field_html, $f ) {
359 $html = '<div class="wpf-field wpf-field-type-' . esc_attr( $f['type'] ) . ' wpf-field-name-' . esc_attr( $f['field_class'] ) . ' ' . esc_attr( $f['required_class'] ) . '" title="' . esc_attr( $f['title'] ) . '">';
360 if( $f['type'] === 'html' ) {
361 $html .= $field_html;
362 } else {
363 if( $f['label'] || $f['description'] ) {
364 $html .= '<div class="wpf-label-wrap">';
365 if( $f['label'] ) {
366 $html .= '<p class="wpf-label wpfcl-1">' . stripslashes( (string) $f['label'] ) . $f['required_indicator'] . '</p>';
367 }
368 if( $f['description'] ) {
369 $html .= '<div class="wpf-desc wpfcl-2">' . $f['description'] . '</div>';
370 }
371 $html .= '</div>';
372 }
373 $html .= '<div class="wpf-field-wrap">';
374 if( $f['faIcon'] ) {
375 $html .= '<i class="' . esc_attr( $f['faIcon'] ) . ' wpf-field-icon"></i>';
376 }
377 $html .= $field_html;
378 $html .= '</div>';
379 }
380 $html .= '<div class="wpf-field-cl"></div></div>';
381
382 return $html;
383 }
384
385 /**
386 * Default wrapper of input HTML
387 *
388 * @param string $field_html input HTML
389 * @param array $f field arguments
390 *
391 * @return string wrapped field input HTML
392 */
393 public function field_wrap_default( $field_html, $f ) {
394 $html = '<div class="wpf-field wpf-field-type-' . esc_attr( $f['type'] ) . ' wpf-field-name-' . esc_attr( $f['field_class'] ) . ' ' . esc_attr( $f['required_class'] ) . '">';
395 if( $f['type'] === 'html' ) {
396 $html .= $field_html;
397 } else {
398 if( $f['label'] || $f['description'] ) {
399 $html .= '<div class="wpf-label-wrap" style="float: none; width: 100%;">';
400 if( $f['faIcon'] ) $html .= '<i class="' . esc_attr( $f['faIcon'] ) . '"></i>';
401 if( $f['label'] ) {
402 $html .= '<p class="wpf-label wpfcl-1" style="display: inline-block; margin-left: 5px;">' . stripslashes( (string) $f['label'] ) . $f['required_indicator'] . '</p>';
403 }
404
405 if( wpfval( $f, 'type' ) === 'file' && ( $file_size = wpfval( $f, 'fileSize' ) ) ) {
406 $html .= '<em style="opacity: 0.8;"> (' . wpforo_phrase( 'max allowed file size', false, 'lower' ) . ' ' . $file_size . 'MB)</em>';
407 }
408
409 if( $f['description'] ) {
410 $html .= '<div class="wpf-desc wpfcl-2" style="display: inline-block; margin-left: 5px;">' . $f['description'] . '</div>';
411 }
412 $html .= '</div>';
413 } else {
414 if( $f['faIcon'] ) $html .= '<i class="' . esc_attr( $f['faIcon'] ) . '"></i>';
415 }
416 $html .= '<div class="wpf-field-wrap" style="width: 100%">';
417 $html .= $field_html;
418 $html .= '</div>';
419 }
420 $html .= '<div class="wpf-field-cl"></div></div>';
421
422 return $html;
423 }
424
425 /**
426 * File - Field builder
427 *
428 * @param array $f field arguments
429 *
430 * @return string field HTML
431 */
432 public function field_file( $f ) {
433 $field_html = '';
434 $extensions = '';
435 if( $f['fileExtensions'] ) {
436 foreach( $f['fileExtensions'] as $key => $ext ) {
437 if( strpos( (string) $ext, '.' ) === false ) {
438 $f['fileExtensions'][ $key ] = '.' . $ext;
439 }
440 }
441 $f['fileExtensions'] = implode( ', ', $f['fileExtensions'] );
442 if( $f['fileExtensions'] ) $extensions = ' accept="' . esc_attr( $f['fileExtensions'] ) . '" ';
443 }
444 if( $f['value'] ) {
445 $f['isRequired'] = '';
446 if( is_array( $f['value'] ) ) {
447 $file_name = (string) wpfval( $f['value'], 'filename' );
448 $f['value'] = (string) wpfval( $f['value'], 'fileurl' );
449 } else {
450 $file_name = basename( (string) $f['value'] );
451 }
452 $extension = pathinfo( $f['value'], PATHINFO_EXTENSION );
453 $f['value'] = wpforo_fix_upload_url( $f['value'] );
454
455 if( wpfval( WPF()->current_object['user'], 'userid' ) ) {
456 $delete = '?wpfaction=ucf_file_delete&foro_f=' . $f['name'] . '&foro_u=' . WPF()->current_object['user']['userid'];
457 $delete_url = wp_nonce_url( $delete, 'wpforo_delete_profile_field', 'foro_n' );
458 $delete_html = ' &nbsp;|&nbsp; <a href="' . $delete_url . '" title="' . wpforo_phrase( 'Delete this file', false ) . '" onclick="return confirm(\'' . wpforo_phrase( 'Are you sure you want to delete this file?', false ) . '\')"><i class="fas fa-times" style="color:#e86666; font-size:14px; line-height:14px;"></i></a>';
459 } else {
460 $delete_html = ' &nbsp;|&nbsp; <span data-fieldkey="' . $f['fieldKey'] . '" style="cursor: pointer;" class="wpforo-delete-custom-file" title="' . wpforo_phrase( 'Delete this file', false ) . '"><i class="fas fa-times" style="color:#e86666; font-size:14px; line-height:14px;"></i></span>';
461 }
462
463 if( $extension && wpforo_is_image( $extension ) ) {
464 $field_html .= '<div class="wpf-field-file-wrap" style="margin-bottom: 10px;">
465 <img src="' . esc_url_raw( $f['value'] ) . '" alt="' . esc_attr( $file_name ) . '" title="' . esc_attr( $file_name ) . '" style="max-width:100px; max-height:100px"/>
466 <div class="wpf-field-file-name">
467 <i class="fas fa-paperclip"></i>
468 <a href="' . esc_url_raw( $f['value'] ) . '" title="' . esc_attr( $file_name ) . '" target="_blank">' . esc_attr( $file_name ) . '</a>' . $delete_html . '
469 </div>
470 </div>';
471 } else {
472 $field_html .= '<div class="wpf-field-file-wrap" style="margin-bottom: 10px;">
473 <div class="wpf-field-file-name">
474 <i class="fas fa-paperclip"></i>
475 <a href="' . esc_url_raw( $f['value'] ) . '" title="' . esc_attr( $file_name ) . '" target="_blank">' . esc_attr( $file_name ) . '</a>' . $delete_html . '
476 </div>
477 </div>';
478 }
479 }
480 $field_html .= '<input type="file" name="' . esc_attr( $f['fieldName'] ) . '" value=""
481 id="' . esc_attr( $f['fieldId'] ) . '" class="' . esc_attr( $f['fieldId'] ) . '"
482 ' . $f['isRequired'] . ' ' . $f['isDisabled'] . ' ' . $f['attributes'] . ' ' . $extensions . ' />';
483
484 return $field_html;
485 }
486
487 private function form_extra( $f ) {
488 $form_type = wpfval( $f, 'form_type' );
489 switch( $form_type ) {
490 case 'topic':
491 if( $forum = wpfval( $f, 'meta', 'forum' ) ) {
492 wpforo_topic_form_extra( (int) wpfval( $forum, 'forumid' ), (array) wpfval( $f, 'meta', 'values' ) );
493 }
494 break;
495 case 'post':
496 if( $topic = wpfval( $f, 'meta', 'topic' ) ) {
497 wpforo_reply_form_extra( $topic, (array) wpfval( $f, 'meta', 'values' ) );
498 }
499 break;
500 }
501 }
502
503 /**
504 * Textarea - Field builder
505 *
506 * @param array $f field arguments
507 *
508 * @return string field HTML
509 */
510 public function field_textarea( $f ) {
511 $field_html = '<textarea name="' . esc_attr( $f['fieldName'] ) . '"
512 id="' . esc_attr( $f['fieldId'] ) . '"
513 class="' . esc_attr( $f['fieldId'] ) . '"
514 placeholder="' . esc_attr( $f['placeholder'] ) . '"
515 ' . $f['isRequired'] . ' ' . $f['isDisabled'] . ' ' . $f['attributes'] . '
516 ' . $f['minmax'] . ' data-isbody="' . intval( $f['fieldKey'] === 'body' ) . '">' . esc_textarea( (string) $f['value'] ) . '</textarea>';
517 if( $f['fieldKey'] === 'body' ) {
518 ob_start();
519 $this->form_extra( $f );
520 $field_html .= ob_get_clean();
521 }
522
523 return $field_html;
524 }
525
526 /**
527 * Textarea - Field builder
528 *
529 * @param array $f field arguments
530 *
531 * @return string field HTML
532 */
533 public function field_tinymce( $f ) {
534 WPF()->current_object['load_tinymce'] = true;
535 $f['isRequired'] = '';
536 $f['attributes'] .= ' data-wpforoeditor="tinymce" ';
537 if( empty( $f['wp_editor_settings'] ) ) $f['wp_editor_settings'] = WPF()->tpl->editor_buttons();
538 $f['wp_editor_settings'] = apply_filters( 'wpforo_form_field_tinymce_editor_settings', $f['wp_editor_settings'], $f );
539 wp_localize_script( 'wpforo-frontend-js', 'wpforo_editor_settings_' . esc_attr( $f['fieldId'] ), $f['wp_editor_settings'] );
540
541 return $this->field_textarea( $f );
542 }
543
544 /**
545 * Password - Field builder
546 *
547 * @param array $f field arguments
548 *
549 * @return string field HTML
550 */
551 public function field_password( $f ) {
552 $field_html = '';
553 if( $f['template'] === 'account' ) {
554 $f['isRequired'] = 0;
555 $f['label'] = wpforo_phrase( 'Old password', false );
556 $f['description'] = '';
557 $field_html .= '<input type="password"
558 name="' . esc_attr( $f['varname'] ) . '[old_pass]"
559 value="" id="' . esc_attr( $f['fieldId'] ) . '-old"
560 class="' . esc_attr( $f['fieldId'] ) . '"
561 placeholder="' . esc_attr( wpforo_phrase( 'Old password', false ) ) . '"
562 ' . $f['isDisabled'] . ' ' . $f['attributes'] . '/>
563 <i class="fas fa-eye-slash wpf-show-password"></i>';
564 }
565 $p1 = '1';
566 $p2 = '2';
567 if( ! empty( $f['varname'] ) ) {
568 $f['fieldName'] = $f['varname'] . '[' . $f['name'] . $p1 . ']';
569 } else {
570 $f['fieldName'] = $f['name'] . $p1;
571 }
572 if( $f['template'] === 'account' ) {
573 $f['label'] = wpforo_phrase( 'New', false ) . ' ' . wpforo_phrase( $f['label'], false, 'lower' );
574 $f['placeholder'] = wpforo_phrase( 'New', false ) . ' ' . wpforo_phrase( $f['placeholder'], false, 'lower' );
575 }
576 if( in_array( $f['template'], ['account','register'], true ) ) {
577 if( $f['template'] === 'account' ) $field_html .= '<div style="position:relative"><i class="' . esc_attr( $f['faIcon'] ) . ' wpf-field-icon"></i>';
578 $field_html .= '<input type="password"
579 name="' . esc_attr( $f['fieldName'] ) . '"
580 value="" id="' . esc_attr( $f['fieldId'] ) . '-new1"
581 class="' . esc_attr( $f['fieldId'] ) . '"
582 placeholder="' . esc_attr( $f['placeholder'] ) . '"
583 ' . $f['isDisabled'] . ' ' . $f['attributes'] . ' ' . $f['minmax'] . '/>';
584 if( $f['template'] === 'register' ) {
585 $field_html .= '<i class="fas fa-eye-slash wpf-show-password"></i>';
586 }
587 if( $f['template'] === 'account' ) {
588 $field_html .= '</div>';
589 }
590 $f['label'] = wpforo_phrase( 'Confirm Password', false );
591 $f['placeholder'] = wpforo_phrase( 'Confirm Password', false );
592 $f['description'] = '';
593 $f['fieldName'] = ( ! empty( $f['varname'] ) ? $f['varname'] . '[' . $f['name'] . $p2 . ']' : $f['name'] . $p2 );
594 $field_html .= '<div style="position:relative"><i class="' . esc_attr( $f['faIcon'] ) . ' wpf-field-icon"></i>';
595 $field_html .= '<input type="password"
596 name="' . esc_attr( $f['fieldName'] ) . '"
597 value="" id="' . esc_attr( $f['fieldId'] ) . '-new2"
598 class="' . esc_attr( $f['fieldId'] ) . '"
599 placeholder="' . esc_attr( $f['placeholder'] ) . '"
600 ' . $f['isDisabled'] . ' ' . $f['attributes'] . ' ' . $f['minmax'] . '/>';
601 $field_html .= '</div>';
602 }
603
604 return $field_html;
605 }
606
607 /**
608 * Checkbox - Field builder
609 *
610 * @param array $f field arguments
611 *
612 * @return string field HTML
613 */
614 public function field_checkbox( $f ) {
615 $i = 0;
616 $field_html = '';
617 $f['value'] = $this->build_array_value( $f['value'], "\n" );
618 $f['values'] = $this->build_array_using_string_rows( $f['values'] );
619 if( ! empty( $f['values'] ) ) {
620 $item_field_name = $f['fieldName'] . '[]';
621 $use_default_selected = ( trim( (string) $f['isRequired'] ) && ! $f['value'] );
622 $f['isRequired'] = ( count( $f['values'] ) == 1 ) ? $f['isRequired'] : '';
623 $field_html .= '<input type="hidden" name="' . esc_attr( $f['fieldName'] ) . '" value="">';
624 foreach( $f['values'] as $row ) {
625 $item = $this->build_item_value_lable( $row );
626 $id = $f['fieldId'] . '_' . ++ $i;
627 $field_html .= '<div class="wpf-field-item">';
628 $item_html = '<input type="checkbox" ' . ( $use_default_selected ? ( $item['default_selected'] ? ' checked ' : '' ) : $this->check( $item['value'], $f['value'] ) ) . '
629 name="' . esc_attr( $item_field_name ) . '"
630 value="' . esc_attr( $item['value'] ) . '"
631 id="' . esc_attr( $id ) . '"
632 class="wpf-input-checkbox ' . esc_attr( $f['class'] ) . '"
633 ' . $f['isDisabled'] . ' ' . $f['isRequired'] . ' ' . $f['attributes'] . '>';
634 $field_html .= $this->build_label( $item['label'], $item_html, $f, $id );
635 $field_html .= '</div>';
636 }
637 }
638
639 return $field_html;
640 }
641
642 /**
643 * Radio - Field builder
644 *
645 * @param array $f field arguments
646 *
647 * @return string field HTML
648 */
649 public function field_radio( $f ) {
650 $i = 0;
651 $field_html = '';
652 $f['value'] = $this->build_array_value( $f['value'], "\n" );
653 $f['values'] = $this->build_array_using_string_rows( $f['values'] );
654 $use_default_selected = ( trim( (string) $f['isRequired'] ) && ! $f['value'] );
655 if( ! empty( $f['values'] ) ) {
656 foreach( $f['values'] as $row ) {
657 $item = $this->build_item_value_lable( $row );
658 $id = $f['fieldId'] . '_' . ++ $i;
659 $field_html .= '<div class="wpf-field-item">';
660 $item_html = '<input type="radio" ' . ( $use_default_selected ? ( $item['default_selected'] ? ' checked ' : '' ) : $this->check( $item['value'], $f['value'] ) ) . '
661 name="' . esc_attr( $f['fieldName'] ) . '"
662 value="' . esc_attr( $item['value'] ) . '"
663 id="' . esc_attr( $id ) . '"
664 class="wpf-input-radio ' . esc_attr( $f['class'] ) . '"
665 ' . $f['isDisabled'] . ' ' . $f['isRequired'] . ' ' . $f['attributes'] . ' />';
666 $field_html .= $this->build_label( $item['label'], $item_html, $f, $id );
667 $field_html .= '</div>';
668 }
669 }
670
671 return $field_html;
672 }
673
674 /**
675 * Radio - Field builder
676 *
677 * @param array $f field arguments
678 *
679 * @return string field HTML
680 */
681 public function field_select( $f ) {
682 $field_html = '';
683 $f['value'] = $this->build_array_value( $f['value'], "\n" );
684 $f['values'] = $this->build_array_using_string_rows( $f['values'] );
685 $use_default_selected = ( trim( (string) $f['isRequired'] ) && ! $f['value'] );
686 if( ! empty( $f['values'] ) ) {
687 $field_html .= '<select name="' . esc_attr( $f['fieldName'] ) . '"
688 id="' . esc_attr( $f['fieldId'] ) . '"
689 class="' . esc_attr( $f['class'] ) . '"
690 ' . $f['isMultiChoice'] . ' ' . $f['isDisabled'] . '
691 ' . $f['isRequired'] . ' ' . $f['attributes'] . '>';
692 if( ! $f['isRequired'] ) {
693 $field_html .= apply_filters( 'wpforo_form_field_select_first_option', '<option value="">' . wpforo_phrase( '--- Choose ---', false ) . '</option>', $f );
694 }
695 foreach( $f['values'] as $optgroup => $row ) {
696 if( is_array( $row ) && ! empty( $row ) ) {
697 $field_html .= '<optgroup label="' . esc_attr( $optgroup ) . '">';
698 foreach( $row as $sub_row ) {
699 $item = $this->build_item_value_lable( $sub_row );
700 $item['value'] = ( $f['name'] === 'timezone' ) ? str_replace( ' ', '_', $item['value'] ) : $item['value'];
701 $field_html .= '<option value="' . esc_attr( $item['value'] ) . '" ' . ( $use_default_selected ? ( $item['default_selected'] ? ' selected ' : '' ) : $this->select( $item['value'], $f['value'] ) ) . '>' . esc_html( $item['label'] ) . '</option>';
702 }
703 $field_html .= '</optgroup>';
704 } else {
705 $item = $this->build_item_value_lable( $row );
706 $item['value'] = ( $f['name'] === 'timezone' ) ? str_replace( ' ', '_', $item['value'] ) : $item['value'];
707 $field_html .= '<option value="' . esc_attr( $item['value'] ) . '" ' . ( $use_default_selected ? ( $item['default_selected'] ? ' selected ' : '' ) : $this->select( $item['value'], $f['value'] ) ) . '>' . esc_html( $item['label'] ) . '</option>';
708 }
709 }
710 $field_html .= '</select>';
711 }
712
713 return $field_html;
714 }
715
716 public function generate_multi_item( $val, $dvalues = [], $save_button = false ) {
717 return sprintf(
718 '<div class="wpf-multi-item wpf-multi-item-known-%1$d"><span class="wpf-multi-item-value">%2$s</span>%3$s<span class="wpf-multi-item-action wpf-del" title="%4$s"><i class="fa-solid fa-xmark" title="%4$s"></i></span></div>',
719 (int) in_array( $val, $dvalues, true ),
720 $val,
721 ( $save_button ? sprintf( '<span class="wpf-multi-item-action wpf-save-variant" title="%1$s"><i class="fa-regular fa-floppy-disk" title="%1$s"></i></span>', wpforo_phrase( 'Add to pre-defined values', false ) ) : '' ),
722 wpforo_phrase( 'remove', false )
723 );
724 }
725
726 /**
727 * Radio - Field builder
728 *
729 * @param array $f field arguments
730 *
731 * @return string field HTML
732 */
733 public function field_autocomplete( $f ) {
734 $f['value'] = $this->build_array_value( $f['value'], "\n" );
735 $f['values'] = $this->build_array_using_string_rows( $f['values'] );
736 $save_button = (int) wpfval( $f, 'formid' ) > 0 && array_intersect( WPF()->current_user_groupids, (array) wpfval( $f, 'UGroupIdsFrontAddNewDefaultValues' ) );
737
738 return sprintf(
739 '<%1$s data-dbvariants="%16$s" data-formid="%2$d" data-fieldkey="%3$s">%4$s<input type="text" name="%5$s" list="%6$s" class="%7$s" value="%8$s" autocomplete="off" %9$s %10$s %11$s %12$s><span class="wpf-invalid-msg wpf-required">%13$s</span><span class="wpf-invalid-msg wpf-not-allowed-value">%14$s</span>%15$s</%1$s>',
740 ( $f['isMultiChoice'] ? 'wpf-multi-input' : 'wpf-autocomplete-input' ),
741 (int) wpfval( $f, 'formid' ),
742 $f['fieldKey'],
743 ( $f['isMultiChoice'] ? sprintf( '<template id="wpf-multi-item-template">%1$s</template>', $this->generate_multi_item( '', [], $save_button ) ) . implode(
744 '',
745 array_map( function( $val ) use ($f, $save_button){ return $this->generate_multi_item( $val, $f['values'], $save_button ); }, array_filter( $f['value'] ) )
746 ) : '' ),
747 esc_attr( $f['fieldName'] ),
748 esc_attr( $f['fieldId'] ),
749 esc_attr( $f['class'] ),
750 ( !$f['isMultiChoice'] ? current($f['value']) : '' ),
751 $f['isDisabled'],
752 $f['isRequired'],
753 $f['attributes'],
754 ( (int) $f['isAllowedCustomValues'] || $save_button ? 'allow-custom-values' : '' ),
755 wpforo_phrase('This Field is Required', false),
756 wpforo_phrase('Wrong Value (This Field Allows Only Predefined Values)', false),
757 ( ( $values = (array) wpfval( $f, 'values' ) ) ? sprintf(
758 '<datalist id="%1$s">%2$s</datalist>',
759 esc_attr( $f['fieldId'] ),
760 implode('',
761 array_map(
762 function( $value ){
763 return sprintf( '<option value="%1$s"></option>', esc_attr( $value ) );
764 },
765 array_diff( $values, $f['value'] )
766 )
767 )
768 ) : '' ),
769 esc_attr( json_encode( $f['values'] ) )
770 );
771 }
772
773 /**
774 * Usergroup - Field builder
775 *
776 * @param array $f field arguments
777 *
778 * @return string field HTML
779 */
780 public function field_usergroup( $f ) {
781 $field_html = '';
782 if( ! empty( $f['allowedGroupIds'] ) ) {
783 $field_html .= '<select name="' . esc_attr( $f['fieldName'] ) . '"
784 id="' . esc_attr( $f['fieldId'] ) . '"
785 class="' . esc_attr( $f['class'] ) . '"
786 ' . $f['isDisabled'] . ' ' . $f['isRequired'] . ' ' . $f['attributes'] . '>';
787
788 $field_html .= '<option value="">' . wpforo_phrase( '--- Choose ---', false ) . '</option>';
789 foreach( $f['allowedGroupIds'] as $groupid ) {
790 if( $f['value'] != 4 && $groupid == 4 ) continue;
791 if( $group = WPF()->usergroup->get_usergroup( $groupid ) ) {
792 $field_html .= '<option value="' . esc_attr( $groupid ) . '" ' . $this->select( $groupid, $f['value'] ) . '>' . $group['name'] . '</option>';
793 }
794 }
795 $field_html .= '</select>';
796 }
797
798 return $field_html;
799 }
800
801 public function field_secondary_groups( $f ) {
802 $field_html = '';
803 if( $groups = WPF()->usergroup->get_secondary_groups() ) {
804 $allowed_groupids = (array) wpfval( $f, 'allowedGroupIds' );
805 $i = 0;
806 $field_html .= '<input type="hidden" name="' . esc_attr( $f['fieldName'] ) . '" value="">';
807 foreach( $groups as $group ) {
808 if( in_array( $group['groupid'], [ 1, 2, 4 ] ) || ( $allowed_groupids && ! in_array( $group['groupid'], $allowed_groupids ) ) ) continue;
809 $id = $f['fieldId'] . '_' . ++ $i;
810 $field_html .= '<div class="wpf-field-item">';
811 $item_html = '<input type="checkbox" ' . $this->check( $group['groupid'], $f['value'] ) . '
812 name="' . esc_attr( $f['fieldName'] . '[]' ) . '"
813 value="' . esc_attr( $group['groupid'] ) . '"
814 id="' . esc_attr( $id ) . '"
815 class="wpf-input-checkbox ' . esc_attr( $f['class'] ) . '"
816 ' . $f['isDisabled'] . ' ' . $f['attributes'] . '>';
817 $field_html .= $this->build_label( $group['name'], $item_html, $f, $id );
818 $field_html .= '</div>';
819 }
820 }
821
822 return $field_html;
823 }
824
825 /**
826 * Avatar - Field builder
827 *
828 * @param array $f field arguments
829 *
830 * @return string field HTML
831 */
832 public function field_avatar( $f ) {
833 $protocol = is_ssl() ? 'https://' : 'http://';
834 $remote_url = ( $f['value'] && strpos( (string) $f['value'], 'wpforo/avatars' ) === false ) ? $f['value'] : '';
835 $field_html = '<ul>
836 <li>
837 <input ' . $f['isRequired'] . ' name="' . esc_attr( $f['varname'] ) . '[avatar_type]" id="wpfat_gravatar" value="gravatar" ' . ( $f['value'] == '' || $f['value'] == null ? 'checked="checked"' : '' ) . ' type="radio" />&nbsp;
838 <label for="wpfat_gravatar">' . wpforo_phrase( 'Default avatar', false ) . '</label>
839 </li>
840 <li>
841 <input name="' . esc_attr( $f['varname'] ) . '[avatar_type]" id="wpfat_remote" value="remote" ' . ( $f['value'] && strpos( (string) $f['value'], 'wpforo/avatars' ) === false ? 'checked="checked"' : '' ) . ' type="radio" />&nbsp;
842 <label for="wpfat_remote">' . wpforo_phrase( 'Specify avatar by URL:', false ) . '</label>
843 <input autocomplete="off" name="' . esc_attr( $f['varname'] ) . '[avatar_url]" value="' . esc_url( preg_replace('|^//|is', $protocol, (string) $remote_url) ) . '" maxlength="990" data-wpfucf-minmaxlength="1,990" type="url" />
844 </li>';
845 if( ( wpfval( WPF()->current_object, 'template' ) && WPF()->current_object['template'] === 'register' ) || WPF()->usergroup->can( 'upa' ) ) {
846 if( strpos( (string) $f['value'], 'gravatar.com' ) === false && strpos( $f['value'], 'facebook.com' ) === false ) {
847 $url = $f['value'] . '?lm=' . time();
848 }else{
849 $url = '';
850 }
851 $field_html .= '<li>
852 <input name="' . esc_attr( $f['varname'] ) . '[avatar_type]" id="wpfat_custom" value="custom" type="radio" ' . ( ( strpos( $url, 'wpforo/avatars' ) !== false ) ? 'checked' : '' ) . ' />&nbsp;
853 <label for="wpfat_custom"> ' . wpforo_phrase( 'Upload an avatar', false ) . '</label>' . ( strpos( $url, 'wpforo/avatars' ) !== false ? '<br />
854 <img src="' . esc_url( (string) $url ) . '" class="wpf-custom-avatar-img" alt="avatar">' : '' ) . '&nbsp;
855 <input class="wpf-custom-avatar" name="avatar" type="file" />&nbsp;</li>';
856 }
857 $field_html .= '</ul> <script type="text/javascript">jQuery(document).ready(function($){$( "input[name=\'' . esc_attr( $f['varname'] ) . '\[avatar_url\]\']" ).on("click", function(){$( "#wpfat_remote" ).prop(\'checked\', true);}); $( "input[name=\'avatar\']" ).on("click", function(){$( "#wpfat_custom" ).prop(\'checked\', true);});});</script>';
858
859 return $field_html;
860 }
861
862 /**
863 * HTML - Field builder
864 *
865 * @param array $f field arguments
866 *
867 * @return string field HTML
868 */
869 public function field_html( $f ) {
870 return stripslashes( do_shortcode( wpforo_apply_ucf_shortcode( (string) $f['html'] ) ) );
871 }
872
873 /**
874 * URL - Field builder
875 *
876 * @param array $f field arguments
877 *
878 * @return string field HTML
879 */
880 public function field_url( $f ) {
881 return '<input type="url" value="' . esc_url_raw( $f['value'] ) . '"
882 name="' . esc_attr( $f['fieldName'] ) . '"
883 id="' . esc_attr( $f['fieldId'] ) . '"
884 class="' . esc_attr( $f['class'] ) . '"
885 placeholder="' . esc_attr( $f['placeholder'] ) . '"
886 ' . $f['isRequired'] . ' ' . $f['isDisabled'] . ' ' . $f['attributes'] . ' ' . $f['minmax'] . '/>';
887 }
888
889 /**
890 * Nickname - Field builder
891 *
892 * @param array $f field arguments
893 *
894 * @return string field HTML
895 */
896 public function field_nicename( $f ) {
897 return '<input type="text" value="' . esc_attr( urldecode( (string) $f['value'] ) ) . '"
898 name="' . esc_attr( $f['fieldName'] ) . '"
899 id="' . esc_attr( $f['fieldId'] ) . '"
900 class="' . esc_attr( $f['class'] ) . '"
901 placeholder="' . esc_attr( $f['placeholder'] ) . '"
902 ' . $f['isRequired'] . ' ' . $f['isDisabled'] . ' ' . $f['attributes'] . ' ' . $f['minmax'] . '/>';
903 }
904
905 /**
906 * Text - Field builder
907 *
908 * @param array $f field arguments
909 *
910 * @return string field HTML
911 */
912 public function field_text( $f ) {
913 return '<input type="' . esc_attr( $f['type'] ) . '"
914 value="' . esc_attr( $f['value'] ) . '"
915 name="' . esc_attr( $f['fieldName'] ) . '"
916 id="' . esc_attr( $f['fieldId'] ) . '"
917 class="' . esc_attr( $f['class'] ) . '"
918 placeholder="' . esc_attr( $f['placeholder'] ) . '"
919 ' . $f['isRequired'] . ' ' . $f['isDisabled'] . ' ' . $f['attributes'] . ' ' . $f['minmax'] . '/>';
920 }
921
922 public function field_tel( $f ) {
923 return '<input type="' . esc_attr( $f['type'] ) . '"
924 value="' . esc_attr( $f['value'] ) . '"
925 name="' . esc_attr( $f['fieldName'] ) . '"
926 id="' . esc_attr( $f['fieldId'] ) . '"
927 class="' . esc_attr( $f['class'] ) . '"
928 placeholder="' . esc_attr( $f['placeholder'] ) . '"
929 ' . $f['isRequired'] . ' ' . $f['isDisabled'] . ' ' . $f['attributes'] . ' ' . $f['minmax'] . '
930 pattern="[0-9]*">';
931 }
932
933 /**
934 * Prepares displayed values for Profile fields
935 *
936 * @param array $f field arguments
937 * @param int $userid
938 *
939 * @return array prepared values
940 */
941 public function prepare_values( $f, $userid = 0 ) {
942 switch( $f['type'] ) {
943 case 'textarea':
944 $f['value'] = wpforo_kses( wpforo_decode( $f['value'] ) );
945 break;
946 case 'date':
947 // $f['value'] = wpforo_date($f['value'], 'date', false);
948 break;
949 case 'datetime':
950 $f['value'] = wpforo_date( $f['value'], 'datetime', false );
951 break;
952 case 'url':
953 $f['value'] = sprintf( '<a href="%1$s" target="_blank" rel="nofollow">%2$s</a>', $f['value'], $f['value'] );
954 break;
955 case 'email':
956 $f['value'] = sprintf( '<a href="mailto:%1$s" rel="nofollow">%2$s</a>', $f['value'], $f['value'] );
957 break;
958 case 'phone':
959 $f['value'] = sprintf( '<a href="tel:%1$s" rel="nofollow">%2$s</a>', $f['value'], $f['value'] );
960 break;
961 case 'file':
962 if( ! empty( $f['value'] ) ) {
963 if( is_array( $f['value'] ) ) {
964 $file_name = (string) wpfval( $f['value'], 'filename' );
965 $f['value'] = (string) wpfval( $f['value'], 'fileurl' );
966 } else {
967 $file_name = basename( (string) $f['value'] );
968 }
969 $f['value'] = wpforo_fix_upload_url( $f['value'] );
970 $file_url = esc_url_raw( $f['value'] );
971 $file_name = esc_attr( $file_name );
972 $extension = pathinfo( $f['value'], PATHINFO_EXTENSION );
973 if( wpforo_is_image( $extension ) ) {
974 $f['value'] = sprintf( '<a href="%1$s" target="_blank" title="%2$s"><img src="%1$s" alt="%2$s" class="wpf-field-file-img" style="max-width:120px; max-height:120px"></a>', $file_url, $file_name );
975 } elseif( wpforo_is_audio( $extension ) ) {
976 $f['value'] = sprintf( '<audio src="%1$s" controls title="%2$s"></audio>', $file_url, $file_name );
977 } elseif( wpforo_is_video( $extension ) ) {
978 $f['value'] = sprintf( '<video src="%1$s" controls title="%2$s"></video>', $file_url, $file_name );
979 } else {
980 $f['value'] = sprintf( '<a href="%s" target="_blank">%s</a>', $file_url, $file_name );
981 }
982 }
983 break;
984 case 'avatar':
985 $f['value'] = ( WPF()->usergroup->can( 'va' ) && wpforo_setting( 'profiles', 'avatars' ) ) ? WPF()->member->get_avatar_html( $f['value'], $userid ) : '';
986 break;
987 case 'color':
988 if( $f['value'] ) {
989 $f['value'] = '<input type="color" value="' . $f['value'] . '" disabled title="' . $f['value'] . '" style="min-width: 100px; min-height: 25px;">';
990 }
991 break;
992 }
993 switch( $f['name'] ) {
994 case 'skype':
995 $f['value'] = sprintf( '<a href="skype:%s?userinfo" rel="nofollow">%s</a>', $f['value'], $f['value'] );
996 break;
997 case 'location':
998 $f['value'] = sprintf( '<a href="//maps.google.com/?q=%s" target="_blank" rel="nofollow">%s</a>', $f['value'], $f['value'] );
999 break;
1000 case 'signature':
1001 $f['value'] = wpforo_signature( $f['value'], [ 'echo' => 0 ] );
1002 break;
1003 case 'about':
1004 $f['value'] = wpforo_nofollow_tag( $f['value'] );
1005 break;
1006 }
1007
1008 return apply_filters( 'wpforo_form_prepare_values', $f );
1009 }
1010
1011 /**
1012 * Prepares arguments
1013 *
1014 * @param array $f field arguments
1015 *
1016 * @return array prepared values
1017 */
1018 public function prepare_args( $f ) {
1019
1020 $is_owner = $this->owner();
1021
1022 //field_class
1023 $f['field_class'] = sanitize_text_field( $f['name'] );
1024
1025 //varname
1026 $f['varname'] = $f['isDefault'] ? (string) wpfval( $this->current, 'varname' ) : 'data';
1027
1028 //template
1029 $f['template'] = ( isset( $this->current['template'] ) ) ? $this->current['template'] : WPF()->current_object['template'];
1030
1031 //value
1032 $f['value'] = ( isset( $this->current['value'][ $f['name'] ] ) ) ? $this->current['value'][ $f['name'] ] : $f['value'];
1033 if( ! $f['isDefault'] && $f['varname'] ) {
1034 $f['value'] = ( isset( $this->current['value'][ $f['varname'] ][ $f['name'] ] ) ) ? $this->current['value'][ $f['varname'] ][ $f['name'] ] : $f['value'];
1035 }
1036 $f['value'] = wpforo_unslashe( $f['value'] );
1037 $f['value'] = wpforo_decode( $f['value'] );
1038
1039 if( $f['name'] === 'user_nicename' ) {
1040 $f['value'] = urldecode( (string) $f['value'] );
1041 }
1042
1043 //allowedGroupIds
1044 $groups = [];
1045 if( ! empty( $f['allowedGroupIds'] ) ) $groups = $this->build_array_value( $f['allowedGroupIds'] );
1046 if( $f['type'] === 'usergroup' ) {
1047 if( ! $is_owner && wpforo_current_user_is( 'admin' ) ) {
1048 $groups = WPF()->usergroup->get_usergroups( 'groupid' );
1049 } elseif( $is_owner && ! in_array( WPF()->current_user_groupid, $f['allowedGroupIds'] ) ) {
1050 $groups = [];
1051 }
1052 }
1053 $f['allowedGroupIds'] = array_filter( $groups );
1054
1055 //isRequired
1056 if( $f['isRequired'] ) {
1057 $f['required_class'] = ' wpf-field-required ';
1058 $f['required_indicator'] = ' <span class="wpf-field-required-icon" title="' . esc_attr( wpforo_phrase( 'Required field', false ) ) . '">*</span>';
1059 } else {
1060 $f['required_class'] = '';
1061 $f['required_indicator'] = '';
1062 }
1063
1064 return $f;
1065 }
1066
1067 /**
1068 * @param array $f field arguments
1069 *
1070 * @return array prepared values
1071 */
1072 public function prepare_field_args( $f ) {
1073 //faIcon
1074 $f['faIcon'] = trim( (string) $f['faIcon'] );
1075 if( strpos( $f['faIcon'], ' ' ) === false ) $f['faIcon'] = 'fas ' . $f['faIcon'];
1076
1077 //isRequired
1078 $f['isRequired'] = ( $f['isRequired'] ) ? ' required="required" ' : '';
1079
1080 //isDisabled
1081 $f['isDisabled'] = ( $f['isDisabled'] ) ? ' disabled="disabled" ' : '';
1082
1083 //fieldName | new key in args
1084 $f['fieldName'] = ( ! empty( $f['varname'] ) ? $f['varname'] . '[' . $f['name'] . ']' : $f['name'] );
1085
1086 //isMultiChoice
1087 if( $f['isMultiChoice'] ) {
1088 $f['fieldName'] .= '[]';
1089 $f['isMultiChoice'] = ' multiple="multiple" ';
1090 } else {
1091 $f['isMultiChoice'] = '';
1092 }
1093
1094 //fieldId | new key in args
1095 $f['fieldId'] = ( $f['varname'] ? $f['varname'] . '_' : '' ) . ( $f['id'] ?: $f['name'] );
1096 $f['fieldId'] = uniqid( $f['fieldId'] . '_' );
1097
1098 //minLength & maxLength
1099 $f['minLength'] = ( $f['minLength'] ) ? intval( $f['minLength'] ) : '';
1100 $f['maxLength'] = ( $f['maxLength'] ) ? intval( $f['maxLength'] ) : '';
1101 if( $f['minLength'] ) {
1102 $minLength_attr = ( $f['type'] === 'date' || $f['type'] === 'number' || $f['type'] === 'range' ) ? ' min="' . $f['minLength'] . '" ' : ' minlength="' . $f['minLength'] . '" ';
1103 }
1104 if( $f['maxLength'] ) {
1105 $maxLength_attr = ( $f['type'] === 'date' || $f['type'] === 'number' || $f['type'] === 'range' ) ? ' max="' . $f['maxLength'] . '" ' : ' maxlength="' . $f['maxLength'] . '" ';
1106 }
1107 $f['minmax'] = isset( $minLength_attr ) ? $minLength_attr : '';
1108 $f['minmax'] .= isset( $maxLength_attr ) ? ' ' . $maxLength_attr : '';
1109
1110 //attributes
1111 $f['attributes'] .= ' autocomplete="off"';
1112
1113 if( wpfkey( $f, 'fileExtensions' ) ) {
1114 if( is_scalar( $f['fileExtensions'] ) ) $f['fileExtensions'] = explode( ',', $f['fileExtensions'] );
1115 $f['fileExtensions'] = array_filter( $f['fileExtensions'] );
1116 }
1117
1118 return $f;
1119 }
1120
1121 /**
1122 * @param array $file_data
1123 * @param int $userid
1124 *
1125 * @return array
1126 */
1127 public function prepare_file_args( $file_data, $userid = 1 ) {
1128 $file = [ 'files' => [], 'fields' => [] ];
1129 $userid = $userid ?: WPF()->current_userid;
1130
1131 if( ! empty( $file_data ) ) {
1132 foreach( $file_data as $file_field_name => $file_name ) {
1133 $field_name_folder = substr( (string) $file_field_name, 6 );
1134 $file_upload_dir = wpforo_fix_dir_sep( WPF()->folders['wp_upload']['dir'] . $this->field_dir . $userid . DIRECTORY_SEPARATOR . $field_name_folder ) . DIRECTORY_SEPARATOR;
1135 $file_path = $file_upload_dir . $file_name;
1136 $file['files'][ $file_field_name ] = $file_path;
1137 $file['fields'][ $file_field_name ] = $this->field_dir . $userid . '/' . $field_name_folder . '/' . $file_name;
1138 wp_mkdir_p( $file_upload_dir );
1139 }
1140 }
1141
1142 return $file;
1143 }
1144
1145 public function check( $needle, $haystack ) {
1146 if( is_scalar( $haystack ) ) $haystack = explode( ',', $haystack );
1147
1148 return in_array( $needle, (array) $haystack ) ? 'checked' : '';
1149 }
1150
1151 public function select( $needle, $haystack ) {
1152 return in_array( $needle, (array) $haystack ) ? 'selected' : '';
1153 }
1154
1155 public function build_label( $item_label, $item_html, $f, $for = '' ) {
1156 $label = '<label for="' . $for . '" class="wpf-' . $f['type'] . '-label">' . stripslashes( (string) $item_label ) . '</label>';
1157 $field_html = $f['isLabelFirst'] ? $label . '&nbsp;' . $item_html : $item_html . '&nbsp;' . $label;
1158 if( $f['isWrapItem'] ) $field_html = '<label for="' . $for . '">' . $field_html . '</label>';
1159
1160 return $field_html;
1161 }
1162
1163 public function build_item_value_lable( $string, $sep = '=>' ) {
1164 $string = trim( (string) $string );
1165 $count = 0;
1166 $string = preg_replace( '#^\[(.+?)]$#isu', '$1', (string) $string, 1, $count );
1167 $default_selected = (bool) $count;
1168 $data = explode( $sep, $string );
1169 $value = wpfkey( $data, 0 ) ? trim( (string) $data[0] ) : 'no_value';
1170 $label = wpfkey( $data, 1 ) ? trim( (string) $data[1] ) : $value;
1171
1172 return compact( 'value', 'label', 'default_selected' );
1173 }
1174
1175 public function build_array_value( $var, $sep = ',' ) {
1176 if( is_scalar( $var ) ) {
1177 $var = trim( (string) $var );
1178 if( ! strlen( (string) $var ) ) return [];
1179 }
1180 if( is_serialized( $var ) ) {
1181 $var = unserialize( $var );
1182 } elseif( is_scalar( $var ) && strpos( (string) $var, $sep ) !== false ) {
1183 $var = explode( $sep, $var );
1184 }
1185
1186 return array_map( 'trim', (array) $var );
1187 }
1188
1189 public function build_array_using_string_rows( $string, $regexp = '' ) {
1190 if( is_scalar( $string ) ){
1191 if( ! $regexp ) $regexp = '#' . preg_quote( PHP_EOL ) . '#isu';
1192 return array_filter( preg_split( $regexp, $string ) );
1193 }
1194
1195 return $string;
1196 }
1197
1198 public function sanitize( $data, $fields ) {
1199 $types = $this->field_types( $fields );
1200 if( ! empty( $data ) && ! empty( $types ) ) {
1201 foreach( $data as $name => $value ) {
1202 if( wpfval( $types, $name ) ) {
1203 $data[ $name ] = $this->sanitize_field( $value, $types[ $name ], $name );
1204 }
1205 }
1206 }
1207
1208 return $data;
1209 }
1210
1211 public function sanitize_field( $value, $type = 'text', $name = '' ) {
1212 if( ! is_null( $value ) ) {
1213 if( $type === 'text' ) {
1214 $value = sanitize_text_field( $value );
1215 if( $name === 'user_nicename' ) {
1216 $value = sanitize_title( sanitize_user( $value, true ) );
1217 }
1218 } elseif( $type === 'url' ) {
1219 $value = esc_url_raw( $value );
1220 } elseif( $type === 'date' ) {
1221 $value = sanitize_text_field( $value );
1222 } elseif( $type === 'textarea' ) {
1223 $value = stripslashes( wpforo_kses( trim( (string) $value ), 'user_description' ) );
1224 } elseif( $type === 'email' ) {
1225 $value = sanitize_email( $value );
1226 } elseif( $type === 'password' ) {
1227 $value = trim( (string) $value );
1228 } elseif( $type === 'usergroup' ) {
1229 $value = intval( $value );
1230 } elseif( $type === 'radio' ) {
1231 $value = sanitize_text_field( $value );
1232 } elseif( $type === 'checkbox' ) {
1233 if( $name === 'secondary_groupids' ) {
1234 $value = wpforo_sanitize_int( $value );
1235 } else {
1236 $value = wpforo_sanitize_text( $value );
1237 }
1238 } elseif( $type === 'select' ) {
1239 $value = sanitize_text_field( $value );
1240 } elseif( $type === 'color' ) {
1241 $value = sanitize_text_field( $value );
1242 } elseif( $type === 'number' ) {
1243 $value = intval( $value );
1244 } elseif( $type === 'tel' ) {
1245 $value = sanitize_text_field( $value );
1246 } elseif( $type === 'html' ) {
1247 $value = preg_replace( '#<script(.*?)>(.*?)</script>#is', '', (string) $value );
1248 }
1249
1250 if( is_string( $value ) ) {
1251 $value = stripslashes( (string) $value );
1252 }
1253 }
1254
1255 return $value;
1256 }
1257
1258 public function esc_field( $f ) {
1259 if( wpfkey( $f, 'value' ) ) {
1260 $f['value'] = wpforo_trim( $f['value'] );
1261 if( in_array( wpfval( $f, 'type' ), [ 'textarea', 'tinymce' ], true ) ) {
1262 $f['value'] = wpautop( wpforo_kses( stripslashes( (string) $f['value'] ) ) );
1263 } elseif( wpfval( $f, 'type' ) === 'file' ){
1264 $f['value'] = wpfval($f['value'], 'fileurl');
1265 } elseif( $f['name'] === 'timezone' ) {
1266 $f['value'] = str_replace( '_', ' ', $f['value'] );
1267 } elseif( $f['fieldKey'] === 'secondary_groupids' ) {
1268 $f['value'] = WPF()->usergroup->get_secondary_group_names( $f['value'] );
1269 $f['value'] = implode( ', ', $f['value'] );
1270 } elseif( $f['type'] === 'usergroup' && $f['fieldKey'] === 'groupid' && ( $f['value'] = intval( $f['value'] ) ) ) {
1271 if( $group = WPF()->usergroup->get_usergroup( $f['value'] ) ) $f['value'] = wpfval( $group, 'name' );
1272 } elseif( is_array( $f['value'] ) ) {
1273 $f['value'] = array_filter( $f['value'] );
1274 $f['value'] = implode( ', ', $f['value'] );
1275 }
1276 }
1277 $f['value'] = apply_filters( 'wpforo_display_field_value', $f['value'], $f );
1278
1279 return $f;
1280 }
1281
1282 public function validate( &$data, &$fields ) {
1283 $type = '';
1284 $label = '';
1285 $userid = '';
1286 $error = [];
1287 $return = [];
1288 $is_owner = $this->owner();
1289 $template = ( isset( $this->current['template'] ) ) ? $this->current['template'] : WPF()->current_object['template'];
1290 if( empty( $data ) ) $error[] = 'No data submitted';
1291 if( empty( $fields ) ) $error[] = 'User profile fields not found';
1292 if( empty( $error ) ) {
1293 foreach( $fields as $r_key => $rows ) {
1294 foreach( $rows as $c_key => $cols ) {
1295 foreach( $cols as $key => $field ) {
1296 if( wpfval( $field, 'name' ) ) {
1297 $name = $field['name'];
1298 if( ! $template && wpfval( $field, 'template' ) ) $template = $field['template'];
1299 if( wpfval( $field, 'label' ) ) $label = esc_html( $field['label'] );
1300 if( wpfval( $field, 'type' ) ) $type = $field['type'];
1301 if( wpfval( $data, 'userid' ) ) $userid = $data['userid'];
1302 if( $template && $template !== 'register' ) {
1303 if( $template === 'account' && $field['type'] === 'password' ) $field['isRequired'] = 0;
1304 if( ! $this->can_edit( $field ) ) {
1305 unset( $cols[ $key ] );
1306 unset( $data[ $name ] );
1307 unset( $fields[ $r_key ][ $c_key ][ $key ] );
1308 }
1309 }
1310 if( wpfkey( $cols, $key ) && wpfkey( $data, $name ) ) {
1311 $value = $data[ $name ];
1312 if( is_string( $value ) ) {
1313 $value = trim( $value );
1314 $value = htmlspecialchars_decode( $value );
1315 }
1316 if( wpfval( $field, 'isRequired' ) && ! $value ) {
1317 $error[] = $label . ' ' . wpforo_phrase( 'field is required', false, false );
1318 }
1319 if( $value ) {
1320 if( $type === 'number' ) {
1321 if( wpfval( $field, 'minLength' ) ) {
1322 if( (int) $value < $field['minLength'] ) {
1323 $error[] = $label . ' ' . sprintf( wpforo_phrase( 'field value must be at least %d', false, false ), intval( $field['minLength'] ) );
1324 }
1325 }
1326 if( wpfval( $field, 'maxLength' ) ) {
1327 if( (int) $value > $field['maxLength'] ) {
1328 $error[] = $label . ' ' . sprintf( wpforo_phrase( 'field value cannot be greater than %d', false, false ), intval( $field['maxLength'] ) );
1329 }
1330 }
1331 } else {
1332 if( wpfval( $field, 'minLength' ) ) {
1333 if( wpforo_strlen( $value ) < $field['minLength'] ) {
1334 $error[] = $label . ' ' . sprintf( wpforo_phrase( 'field length must be at least %d characters', false, false ), intval( $field['minLength'] ) );
1335 }
1336 }
1337 if( wpfval( $field, 'maxLength' ) ) {
1338 if( wpforo_strlen( $value ) > $field['maxLength'] ) {
1339 $error[] = $label . ' ' . sprintf( wpforo_phrase( 'field length cannot be greater than %d characters', false, false ), intval( $field['maxLength'] ) );
1340 }
1341 }
1342 }
1343 if( $type === 'url' && filter_var( $value, FILTER_VALIDATE_URL ) === false ) {
1344 $error[] = $label . ' ' . wpforo_phrase( 'field value is not a valid URL', false, false );
1345 }
1346 if( $type === 'email' ) {
1347 if( ! is_email( $value ) ) {
1348 $error[] = $label . ' ' . wpforo_phrase( 'Invalid Email address', false, false );
1349 }
1350 if( $name === 'user_email' ) {
1351 $email_owner = email_exists( $value );
1352 if( $email_owner && $email_owner != $userid ) {
1353 $error[] = $label . ' ' . wpforo_phrase( 'This email address is already registered. Please insert another', false, false );
1354 }
1355 }
1356 }
1357 if( $type === 'file' ) {
1358 $extension = pathinfo( $value, PATHINFO_EXTENSION );
1359 $extension = ( function_exists( 'mb_strtolower' ) ) ? mb_strtolower( (string) $extension ) : strtolower( (string) $extension );
1360 if( wpfval( $field, 'fileExtensions' ) ) {
1361 if( $extension ) {
1362 if( ! in_array( $extension, $field['fileExtensions'] ) ) {
1363 $error[] = $label . ' ' . wpforo_phrase( 'file type is not allowed', false, false );
1364 $error[] = sprintf( 'Allowed file types: %s', implode( ', ', $field['fileExtensions'] ) );
1365 }
1366 } else {
1367 $error[] = $label . ' ' . wpforo_phrase( 'file type is not detected', false, false );
1368 }
1369 } else {
1370 $mime_types = get_allowed_mime_types();
1371 $mime_types = array_flip( $mime_types );
1372 if( ! empty( $mime_types ) ) {
1373 $implode_types = implode( '|', $mime_types );
1374 $explode_types = explode( '|', $implode_types );
1375 if( ! in_array( $extension, $explode_types ) ) {
1376 $error[] = $label . ' ' . sprintf( wpforo_phrase( 'file type %s is not allowed', false, false ), $extension );
1377 }
1378 if( ! WPF()->perm->can_attach_file_type( $extension ) ) {
1379 $error[] = 'You are not allowed to attach this file type';
1380 }
1381 }
1382 }
1383 if( wpfval( $field, 'fileSize' ) ) {
1384 if( wpfval( $_FILES, 'data', 'size', $name ) && $_FILES['data']['size'][ $name ] > ( $field['fileSize'] * 1024 * 1024 ) ) {
1385 $error[] = $label . ' ' . wpforo_phrase( 'file is too large', false, false );
1386 $error[] = sprintf( 'Maximum allowed file size is %s MB', $field['fileSize'] );
1387 }
1388 }
1389 }
1390 if( $name === 'user_nicename' ) {
1391 $value = sanitize_text_field( $value );
1392 $user_nicename = sanitize_title( sanitize_user( $value, true ) );
1393 if( ! $user_nicename ) {
1394 $error[] = 'Nickname validation failed';
1395 }
1396 $sql = "SELECT `ID` FROM `" . WPF()->db->users . "` WHERE `ID` != " . intval( $userid ) . " AND `user_nicename` LIKE '" . esc_sql( $user_nicename ) . "'";
1397 if( WPF()->db->get_var( $sql ) ) {
1398 $error[] = 'This nickname is already in use. Please insert another.';
1399 }
1400 }
1401 if( $name === 'groupid' ) {
1402 if( $template !== 'register' ) {
1403 if( $is_owner || ! wpforo_current_user_is( 'admin' ) ) {
1404 $error[] = 'You have no permission to edit Usergroup field';
1405 }
1406 } else {
1407 if( in_array( $value, [ 1, 2, 4 ] ) ) {
1408 $error[] = 'Admin and Moderator Usergroups are not permitted';
1409 } else {
1410 if( wpfval( $field, 'allowedGroupIds' ) ) {
1411 $allowedGroupIds = wpforo_parse_args( $field['allowedGroupIds'] );
1412 if( ! in_array( $value, $allowedGroupIds ) ) {
1413 $error[] = 'The selected Usergroup cannot be set';
1414 }
1415 } else {
1416 $error[] = 'The selected Usergroup is not found in allowed list';
1417 }
1418 }
1419 }
1420 }
1421 if( $name === 'secondary_groupids' ) {
1422 if( $template === 'register' || ( $is_owner && $field['isEditable'] ) || ( ! $is_owner && in_array( WPF()->current_user_groupid, (array) $field['canEdit'] ) ) || wpforo_current_user_is( 'admin' ) ) {
1423 if( ! empty( $value ) && is_array( $value ) ) {
1424 $secondary_groupids = WPF()->usergroup->get_secondary_groupids();
1425 foreach( $value as $secondary_groupid ) {
1426 if( in_array( $secondary_groupid, [ 1, 2, 4 ] ) ) {
1427 $error[] = 'Admin and Moderator Usergroups are not permitted';
1428 }
1429 if( $secondary_groupid && ! in_array( $secondary_groupid, $secondary_groupids ) ) {
1430 $error[] = 'One of the selected Usergroups cannot be set as Secondary';
1431 }
1432 }
1433 }
1434 } else {
1435 $error[] = 'You have no permission to edit Usergroup field';
1436 }
1437 }
1438 }
1439 }
1440 }
1441 }
1442 }
1443 }
1444 }
1445 if( ! empty( $error ) ) {
1446 $return['error'] = $error;
1447
1448 return $return;
1449 } else {
1450 return true;
1451 }
1452 }
1453
1454 public function validate_password( $data ) {
1455 $error = [];
1456 $return = [ 'error' => false ];
1457 if( wpfval( $data, 'old_pass' ) && wpfval( $data, 'user_pass1' ) && wpfval( $data, 'user_pass2' ) ) {
1458 if( $data['user_pass1'] !== $data['user_pass2'] ) {
1459 $error[] = 'New Passwords do not match';
1460 } else {
1461 return true;
1462 }
1463 }
1464 if( ! empty( $error ) ) {
1465 $return['error'] = $error;
1466
1467 return $return;
1468 }
1469
1470 return false;
1471 }
1472
1473 public function field_types( $fields ) {
1474 $name_type_fields = [];
1475 if( ! empty( $fields ) ) {
1476 foreach( $fields as $rows ) {
1477 foreach( $rows as $cols ) {
1478 foreach( $cols as $field ) {
1479 if( wpfval( $field, 'name' ) && wpfval( $field, 'type' ) ) {
1480 $name_type_fields[ $field['name'] ] = $field['type'];
1481 }
1482 }
1483 }
1484 }
1485 }
1486
1487 return $name_type_fields;
1488 }
1489
1490 public function owner( $object_userid = false ): bool {
1491 if( ! $object_userid ) {
1492 if( wpfval( WPF()->current_object, 'user', 'userid' ) ) {
1493 return wpforo_is_owner( WPF()->current_object['user']['userid'] );
1494 } else {
1495 return false;
1496 }
1497 } else {
1498 return wpforo_is_owner( $object_userid );
1499 }
1500 }
1501
1502 /**
1503 * @param array $f
1504 *
1505 * @return bool
1506 */
1507 public function can_add( $f ) {
1508 if( wpfval( $f, 'name' ) ) {
1509 if( $f['name'] === 'signature' && ! wpforo_setting( 'profiles', 'signature' ) ) {
1510 return false;
1511 }
1512 if( $f['name'] === 'avatar' && ( ! wpforo_setting( 'profiles', 'custom_avatars' ) || ! wpforo_setting( 'profiles', 'avatars' ) ) ) {
1513 return false;
1514 }
1515 }
1516
1517 return true;
1518 }
1519
1520 /**
1521 * @param array $f
1522 *
1523 * @return bool
1524 */
1525 public function can_view( $f ) {
1526 return ! ( ! $this->owner() && ! in_array( WPF()->current_user_groupid, $f['canView'] ) );
1527 }
1528
1529 /**
1530 * @param array $f
1531 *
1532 * @return bool
1533 */
1534 public function can_edit( $f ) {
1535 if( wpfval( WPF()->current_object, 'user', 'userid' ) ) {
1536 $is_owner = $this->owner();
1537 $value = wpfkey( $f, 'value' ) ? $f['value'] : false;
1538 $can_edit = wpfkey( $f, 'isEditable' ) ? $f['isEditable'] : false;
1539 $can_moderate = $this->can_moderate( $f );
1540 if( ! $is_owner && ! $can_moderate && WPF()->current_user_groupid !== 1 ) {
1541 return false;
1542 }
1543 if( ! $can_edit && ! $can_moderate && WPF()->current_user_groupid !== 1 && ! $value ) {
1544 return false;
1545 }
1546 if( wpfkey( $f, 'name' ) ) {
1547 if( $f['name'] === 'signature' && ( ! WPF()->usergroup->can( 'ups' ) || ! wpforo_setting( 'profiles', 'signature' ) ) ) {
1548 return false;
1549 }
1550 if( $f['name'] === 'avatar' && ( ! wpforo_setting( 'profiles', 'custom_avatars' ) || ! wpforo_setting( 'profiles', 'avatars' ) ) ) {
1551 return false;
1552 }
1553 if( $f['name'] === 'groupid' && ( WPF()->current_user_groupid != 1 || $is_owner || ! current_user_can( 'administrator' ) ) ) {
1554 return false;
1555 }
1556 }
1557
1558 return true;
1559 } else {
1560 return false;
1561 }
1562 }
1563
1564 /**
1565 * @param array $f
1566 *
1567 * @return bool
1568 */
1569 public function can_moderate( $f ) {
1570 if( empty( $f ) ) return false;
1571 $usergroups_who_can_edit = ! empty( $f['canEdit'] ) ? (array) $f['canEdit'] : [ 1 ];
1572
1573 return in_array( WPF()->current_user_groupid, $usergroups_who_can_edit );
1574 }
1575
1576 public function can_show_value( $f ) {
1577 $return = true;
1578 if( is_string( $f ) ) $f = WPF()->member->get_field( $f );
1579 $f = wpforo_parse_args( $f, $this->default );
1580 if( $f['type'] === 'password' ) $return = false;
1581
1582 return apply_filters( 'wpforo_form_can_show_value', $return, $f );
1583 }
1584 }
1585