Field_Registry::all(), 'starterField' => Field_Registry::normalize( 'text' ), 'direction' => is_rtl() ? 'rtl' : 'ltr', 'i18n' => array( 'addChoice' => __( 'افزودن گزینه', 'mrn-forms' ), 'allowedExtensions' => __( 'پسوندهای مجاز', 'mrn-forms' ), 'addAlternativeField' => __( 'برای استفاده از الزام وابسته، ابتدا یک فیلد ورودی دیگر به فرم اضافه کنید.', 'mrn-forms' ), 'alternativeField' => __( 'فیلد جایگزین', 'mrn-forms' ), 'alwaysRequired' => __( 'همیشه الزامی', 'mrn-forms' ), 'alwaysRequiredHelp' => __( 'کاربر همیشه باید این فیلد را تکمیل کند.', 'mrn-forms' ), 'choice' => __( 'گزینه', 'mrn-forms' ), 'choice1' => __( 'گزینه ۱', 'mrn-forms' ), 'choice2' => __( 'گزینه ۲', 'mrn-forms' ), 'choice3' => __( 'گزینه ۳', 'mrn-forms' ), 'choices' => __( 'گزینه‌ها', 'mrn-forms' ), 'conditionalLogic' => __( 'منطق شرطی', 'mrn-forms' ), 'conditionallyRequired' => __( 'الزامی وقتی فیلد دیگری خالی است', 'mrn-forms' ), 'conditionallyRequiredHelp' => __( 'تکمیل این فیلد به مقدار فیلد جایگزین بستگی دارد.', 'mrn-forms' ), 'confirmAction' => __( 'این عملیات قابل بازگشت نیست. ادامه می‌دهید؟', 'mrn-forms' ), 'content' => __( 'محتوا', 'mrn-forms' ), 'copySuccess' => __( 'کپی شد', 'mrn-forms' ), 'defaultValue' => __( 'مقدار پیش‌فرض', 'mrn-forms' ), 'delete' => __( 'حذف', 'mrn-forms' ), 'deleteField' => __( 'این فیلد حذف شود؟', 'mrn-forms' ), 'description' => __( 'توضیح راهنما', 'mrn-forms' ), 'duplicate' => __( 'تکثیر', 'mrn-forms' ), 'emptyCanvas' => __( 'فیلدها را از ستون کناری بکشید یا روی آن‌ها کلیک کنید.', 'mrn-forms' ), /* translators: %d: Number of fields in the form. */ 'fieldCount' => __( '%d فیلد', 'mrn-forms' ), 'fieldLabel' => __( 'برچسب', 'mrn-forms' ), 'fieldWidth' => __( 'عرض فیلد', 'mrn-forms' ), 'fileSelect' => __( 'انتخاب فایل…', 'mrn-forms' ), /* translators: %s: Field key used in a merge tag. */ 'helpMergeTag' => __( 'برای merge tag: {field:%s}', 'mrn-forms' ), 'hidden' => __( 'پنهان', 'mrn-forms' ), /* translators: %s: Field type label. */ 'input' => __( 'ورودی %s', 'mrn-forms' ), 'layout' => __( 'چیدمان', 'mrn-forms' ), 'maxFileSize' => __( 'حداکثر حجم (MB)', 'mrn-forms' ), 'maximum' => __( 'بیشینه', 'mrn-forms' ), 'maxLength' => __( 'حداکثر نویسه', 'mrn-forms' ), 'minimum' => __( 'کمینه', 'mrn-forms' ), 'minLength' => __( 'حداقل نویسه', 'mrn-forms' ), 'newSection' => __( 'عنوان یک بخش تازه', 'mrn-forms' ), 'noOptions' => __( 'بدون گزینه', 'mrn-forms' ), 'noValue' => __( 'بدون مقدار', 'mrn-forms' ), 'optionalField' => __( 'اختیاری', 'mrn-forms' ), 'optionalFieldHelp' => __( 'تکمیل این فیلد برای کاربر اختیاری است.', 'mrn-forms' ), 'pattern' => __( 'الگوی RegEx', 'mrn-forms' ), 'placeholderContent' => __( 'متن توضیحی یا HTML', 'mrn-forms' ), 'placeholderLong' => __( 'متن بلند…', 'mrn-forms' ), 'requiredField' => __( 'تکمیل این فیلد الزامی است', 'mrn-forms' ), 'requiredWithoutHelp' => __( 'اگر فیلد جایگزین خالی باشد، تکمیل این فیلد الزامی می‌شود.', 'mrn-forms' ), 'requirementMode' => __( 'وضعیت الزامی بودن', 'mrn-forms' ), 'selectField' => __( 'برای ویرایش تنظیمات، یک فیلد را انتخاب کنید.', 'mrn-forms' ), 'selectFieldOption' => __( 'انتخاب فیلد…', 'mrn-forms' ), 'showConditionally' => __( 'نمایش شرطی این فیلد', 'mrn-forms' ), 'startsHere' => __( 'فرم شما از اینجا شروع می‌شود', 'mrn-forms' ), 'uniqueKey' => __( 'کلید یکتا', 'mrn-forms' ), 'unsaved' => __( 'تغییرات ذخیره‌نشده دارید.', 'mrn-forms' ), 'validation' => __( 'اعتبارسنجی', 'mrn-forms' ), 'width25' => __( '۲۵٪', 'mrn-forms' ), 'width33' => __( '۳۳٪', 'mrn-forms' ), 'width50' => __( '۵۰٪', 'mrn-forms' ), 'width66' => __( '۶۶٪', 'mrn-forms' ), 'width75' => __( '۷۵٪', 'mrn-forms' ), 'width100' => __( '۱۰۰٪', 'mrn-forms' ), 'condition' => __( 'شرط', 'mrn-forms' ), 'compareValue' => __( 'مقدار مقایسه', 'mrn-forms' ), 'equals' => __( 'برابر باشد با', 'mrn-forms' ), 'notEquals' => __( 'برابر نباشد با', 'mrn-forms' ), 'contains' => __( 'شامل باشد', 'mrn-forms' ), 'notEmpty' => __( 'خالی نباشد', 'mrn-forms' ), 'empty' => __( 'خالی باشد', 'mrn-forms' ), 'sourceField' => __( 'فیلد مبنا', 'mrn-forms' ), ), ) ); } /** * Add dashboard shortcut on Plugins screen. * * @param string[] $links Existing links. * @return string[] */ public function plugin_links( array $links ): array { array_unshift( $links, '' . esc_html__( 'داشبورد', 'mrn-forms' ) . '' ); return $links; } /** * Operations dashboard. * * @return void */ public function render_dashboard(): void { $this->authorize(); $forms = $this->forms->count(); $published = $this->forms->count( 'published' ); $entries = $this->entries->count(); $unread = $this->entries->count( 0, 'unread' ); $latest = $this->entries->all( array( 'limit' => 6 ) ); $active_forms = $this->forms->all( array( 'status' => 'published', 'limit' => 5, ) ); $this->header( 'dashboard' ); $this->notice(); ?>
stat( __( 'کل فرم‌ها', 'mrn-forms' ), $forms, sprintf( __( '%d فرم منتشرشده', 'mrn-forms' ), $published ), 'forms' ); ?> stat( __( 'کل ارسال‌ها', 'mrn-forms' ), $entries, __( 'آرشیو امن و قابل خروجی', 'mrn-forms' ), 'entries' ); ?> stat( __( 'خوانده‌نشده', 'mrn-forms' ), $unread, __( 'نیازمند بررسی شما', 'mrn-forms' ), 'unread' ); ?> stat( __( 'اعلان ایمیلی', 'mrn-forms' ), __( 'فعال', 'mrn-forms' ), __( 'مدیر و تکمیل‌کننده فرم', 'mrn-forms' ), 'mail' ); ?>

entries_table( $latest, true ); ?>

entries->count( $form['id'] ) ) ); ?>
[mrn_form id=""]
footer(); ?> authorize(); $search = sanitize_text_field( wp_unslash( $_GET['s'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only filter. $forms = $this->forms->all( array( 'search' => $search ) ); $this->header( 'forms' ); $this->notice(); ?>

# · status_badge( $form['status'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> entries->count( $form['id'] ) ); ?> [mrn_form id=""]
footer(); ?> authorize(); $id = absint( $_GET['id'] ?? 0 ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only form selector. $form = $id ? $this->forms->find( $id ) : null; if ( ! $form ) { $form = array( 'id' => 0, 'title' => __( 'فرم تماس جدید', 'mrn-forms' ), 'slug' => '', 'status' => 'draft', 'description' => __( 'لطفاً فرم زیر را تکمیل کنید؛ در اولین فرصت با شما تماس می‌گیریم.', 'mrn-forms' ), 'fields' => Field_Registry::starter_fields(), 'settings' => Form_Repository::default_settings(), 'notifications' => Form_Repository::default_notifications(), ); } $this->header( 'builder', $form['id'] ? __( 'ویرایش فرم', 'mrn-forms' ) : __( 'فرم تازه', 'mrn-forms' ) ); $this->notice(); ?>
#
appearance_panel( $form['settings'] ); ?>
notifications_panel( $form['notifications'], $form['fields'] ); ?>
footer(); ?> authorize(); $entry_id = absint( $_GET['entry_id'] ?? 0 ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( $entry_id ) { $this->render_entry_detail( $entry_id ); return; } $form_id = absint( $_GET['form_id'] ?? 0 ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $status = sanitize_key( $_GET['status'] ?? '' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $entries = $this->entries->all( array( 'form_id' => $form_id, 'status' => $status, 'limit' => 100, ) ); $forms = $this->forms->all(); $this->header( 'entries' ); $this->notice(); ?>

entries_table( $entries ); ?>
footer(); ?> authorize(); $settings = Settings::all(); $this->header( 'settings' ); $this->notice(); ?>

setting_field( 'brand_name', __( 'نام برند', 'mrn-forms' ), $settings['brand_name'] ); ?> setting_field( 'email_logo', __( 'نشانی لوگو', 'mrn-forms' ), $settings['email_logo'], 'url', __( 'تصویر مربع با HTTPS پیشنهاد می‌شود.', 'mrn-forms' ) ); ?> setting_field( 'from_name', __( 'نام فرستنده', 'mrn-forms' ), $settings['from_name'] ); ?> setting_field( 'from_email', __( 'ایمیل فرستنده', 'mrn-forms' ), $settings['from_email'], 'email' ); ?> setting_field( 'admin_email', __( 'ایمیل پیش‌فرض مدیر', 'mrn-forms' ), $settings['admin_email'], 'email', __( 'در merge tag با {admin_email} قابل استفاده است.', 'mrn-forms' ) ); ?>

setting_field( 'rate_limit', __( 'حداکثر ارسال در ۱۰ دقیقه', 'mrn-forms' ), $settings['rate_limit'], 'number', __( 'برای هر IP و هر فرم؛ بین ۱ تا ۵۰.', 'mrn-forms' ) ); ?>
toggle( 'delete_on_uninstall', __( 'حذف کامل داده‌ها هنگام uninstall', 'mrn-forms' ), __( 'فرم‌ها، ارسال‌ها، لاگ ایمیل و تنظیمات برای همیشه حذف می‌شوند.', 'mrn-forms' ), (bool) $settings['delete_on_uninstall'], true ); ?>

toggle( 'collect_attribution', __( 'ثبت منبع بازدید', 'mrn-forms' ), __( 'منبع، کمپین و صفحه فرود در نشست مرورگر و همراه ارسال فرم روی همین سایت ذخیره می‌شوند.', 'mrn-forms' ), (bool) $settings['collect_attribution'] ); ?> toggle( 'analytics_events', __( 'ارسال رویداد به Google Analytics', 'mrn-forms' ), __( 'رویدادهای تماس، واتساپ و ارسال موفق را به gtag موجود در سایت می‌فرستد. افزونه کد گوگل را نصب نمی‌کند و پاسخ فرم یا شماره تماس را نمی‌فرستد.', 'mrn-forms' ), (bool) $settings['analytics_events'] ); ?>
footer(); ?> authorize(); check_admin_referer( 'mrnf_save_form' ); $fields = json_decode( wp_unslash( $_POST['fields_json'] ?? '[]' ), true ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Form_Repository normalizes every decoded field. $settings = json_decode( wp_unslash( $_POST['settings_json'] ?? '{}' ), true ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Form_Repository sanitizes every decoded setting. $notifications = json_decode( wp_unslash( $_POST['notifications_json'] ?? '[]' ), true ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Form_Repository sanitizes every decoded notification. $result = $this->forms->save( array( 'id' => absint( $_POST['id'] ?? 0 ), 'title' => sanitize_text_field( wp_unslash( $_POST['title'] ?? '' ) ), 'status' => sanitize_key( $_POST['status'] ?? 'draft' ), 'description' => sanitize_textarea_field( wp_unslash( $_POST['description'] ?? '' ) ), 'fields' => is_array( $fields ) ? $fields : array(), 'settings' => is_array( $settings ) ? $settings : array(), 'notifications' => is_array( $notifications ) ? $notifications : array(), ) ); if ( is_wp_error( $result ) ) { $this->set_notice( 'error', $result->get_error_message() ); $this->redirect( 'forms' ); } $this->set_notice( 'success', __( 'فرم با موفقیت ذخیره شد.', 'mrn-forms' ) ); wp_safe_redirect( add_query_arg( 'id', $result, $this->page_url( 'builder' ) ) ); exit; } /** * Delete or duplicate a form. * * @return void */ public function form_action(): void { $this->authorize(); $id = absint( $_GET['id'] ?? 0 ); $action = sanitize_key( $_GET['do'] ?? '' ); check_admin_referer( 'mrnf_form_action_' . $id . '_' . $action ); if ( 'delete' === $action ) { $this->forms->delete( $id ); $this->set_notice( 'success', __( 'فرم و ارسال‌های مرتبط حذف شدند.', 'mrn-forms' ) ); } elseif ( 'duplicate' === $action ) { $result = $this->forms->duplicate( $id ); $this->set_notice( is_wp_error( $result ) ? 'error' : 'success', is_wp_error( $result ) ? $result->get_error_message() : __( 'یک نسخه پیش‌نویس ساخته شد.', 'mrn-forms' ) ); } $this->redirect( 'forms' ); } /** * Change or delete an entry. * * @return void */ public function entry_action(): void { $this->authorize(); $id = absint( $_GET['id'] ?? 0 ); $action = sanitize_key( $_GET['do'] ?? '' ); check_admin_referer( 'mrnf_entry_action_' . $id . '_' . $action ); if ( 'delete' === $action ) { $this->entries->delete( $id ); $this->set_notice( 'success', __( 'ارسال حذف شد.', 'mrn-forms' ) ); } elseif ( in_array( $action, array( 'read', 'unread', 'spam' ), true ) ) { $this->entries->set_status( $id, $action ); $this->set_notice( 'success', __( 'وضعیت ارسال به‌روز شد.', 'mrn-forms' ) ); } $this->redirect( 'entries' ); } /** * Generate a UTF-8 BOM CSV compatible with spreadsheet applications. * * @return void */ public function export_entries(): void { $this->authorize(); check_admin_referer( 'mrnf_export_entries' ); $form_id = absint( $_GET['form_id'] ?? 0 ); $entries = $this->entries->all( array( 'form_id' => $form_id, 'limit' => 5000, ) ); $keys = array(); foreach ( $entries as $entry ) { $keys = array_unique( array_merge( $keys, array_keys( $entry['values'] ) ) ); } nocache_headers(); header( 'Content-Type: text/csv; charset=UTF-8' ); header( 'Content-Disposition: attachment; filename=mrn-form-entries-' . gmdate( 'Y-m-d' ) . '.csv' ); $output = fopen( 'php://output', 'wb' ); fwrite( $output, "\xEF\xBB\xBF" ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite -- Streaming a generated download, not writing the filesystem. fputcsv( $output, array_map( array( CSV_Export::class, 'cell' ), array_merge( array( 'ID', 'Form', 'Status', 'Created', 'Traffic source', 'Medium', 'Campaign', 'Landing page', 'External referrer' ), $keys ) ), ',', '"', '' ); foreach ( $entries as $entry ) { $attribution = (array) ( $entry['attribution'] ?? array() ); $row = array( $entry['id'], $entry['form_title'], $entry['status'], $entry['created_at'], Attribution::label( $attribution ), $attribution['medium'] ?? '', $attribution['campaign'] ?? '', $attribution['landing_page'] ?? '', $attribution['referrer'] ?? '', ); foreach ( $keys as $key ) { $value = $entry['values'][ $key ] ?? ''; $row[] = is_array( $value ) ? implode( ' | ', $value ) : $value; } fputcsv( $output, array_map( array( CSV_Export::class, 'cell' ), $row ), ',', '"', '' ); } fclose( $output ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closing the streamed response. exit; } /** * Save global settings. * * @return void */ public function save_settings(): void { $this->authorize(); check_admin_referer( 'mrnf_save_settings' ); $input = isset( $_POST['settings'] ) && is_array( $_POST['settings'] ) ? wp_unslash( $_POST['settings'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Settings::sanitize validates every member. update_option( Settings::OPTION, Settings::sanitize( $input ) ); $this->set_notice( 'success', __( 'تنظیمات ذخیره شد.', 'mrn-forms' ) ); $this->redirect( 'settings' ); } /** * Builder appearance settings. * * @param array $settings Settings. * @return void */ private function appearance_panel( array $settings ): void { ?>
builder_toggle( 'showTitle', __( 'نمایش عنوان فرم', 'mrn-forms' ), $settings['showTitle'] ); ?> builder_toggle( 'showDescription', __( 'نمایش توضیحات فرم', 'mrn-forms' ), $settings['showDescription'] ); ?> builder_toggle( 'buttonFullWidth', __( 'دکمه تمام‌عرض', 'mrn-forms' ), $settings['buttonFullWidth'] ); ?>
builder_toggle( 'storeEntries', __( 'ذخیره ارسال‌ها در صندوق ورودی', 'mrn-forms' ), $settings['storeEntries'] ); ?>builder_toggle( 'emailNotifications', __( 'ارسال اعلان‌های ایمیلی', 'mrn-forms' ), $settings['emailNotifications'] ); ?>
> $notifications Notifications. * @param array> $fields Form fields. * @return void */ private function notifications_panel( array $notifications, array $fields ): void { ?>
{form_title}{entry_id}{site_name}{admin_email}{all_fields}{field:email}
$notification ) : ?> false, 'field' => '', 'operator' => 'equals', 'value' => '', ) ); ?>
entries->find( $entry_id ); if ( ! $entry ) { wp_die( esc_html__( 'ارسال پیدا نشد.', 'mrn-forms' ) ); } $form = $this->forms->find( $entry['form_id'] ); $attribution = (array) ( $entry['attribution'] ?? array() ); if ( 'unread' === $entry['status'] ) { $this->entries->set_status( $entry_id, 'read' ); $entry['status'] = 'read'; } $labels = $form ? array_column( $form['fields'], 'label', 'key' ) : array(); $this->header( 'entries', __( 'جزئیات ارسال', 'mrn-forms' ) ); ?>
status_badge( $entry['status'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>#

$value ) : ?>
entry_value( $value ); ?>
footer(); ?> > $entries Entries. * @param bool $compact Compact mode. * @return void */ private function entries_table( array $entries, bool $compact = false ): void { ?>

status_badge( $entry['status'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>#
array( self::PAGE, __( 'داشبورد', 'mrn-forms' ) ), 'forms' => array( self::PAGE . '-forms', __( 'فرم‌ها', 'mrn-forms' ) ), 'builder' => array( self::PAGE . '-builder', __( 'فرم‌ساز', 'mrn-forms' ) ), 'entries' => array( self::PAGE . '-entries', __( 'صندوق ورودی', 'mrn-forms' ) ), 'settings' => array( self::PAGE . '-settings', __( 'تنظیمات', 'mrn-forms' ) ), ); ?>
__( 'منتشرشده', 'mrn-forms' ), 'draft' => __( 'پیش‌نویس', 'mrn-forms' ), 'archived' => __( 'بایگانی', 'mrn-forms' ), 'unread' => __( 'جدید', 'mrn-forms' ), 'read' => __( 'خوانده‌شده', 'mrn-forms' ), 'spam' => __( 'هرزنامه', 'mrn-forms' ), ); return '' . esc_html( $labels[ $status ] ?? $status ) . ''; } /** * Standard setting input. * * @param string $key Key. * @param string $label Label. * @param int|string $value Value. * @param string $type Input type. * @param string $help Help text. * @return void */ private function setting_field( string $key, string $label, int|string $value, string $type = 'text', string $help = '' ): void { ?> ' . esc_html( $value ) . ''; } elseif ( filter_var( $value, FILTER_VALIDATE_URL ) ) { echo '' . esc_html( $value ) . ''; } else { echo nl2br( esc_html( $value ) ); } } /** * Render a safe entry metadata URL or an em dash. * * @param string $url URL to render. * @return void */ private function entry_url( string $url ): void { if ( '' === $url ) { echo '—'; return; } ?> 'mrnf_form_action', 'id' => $id, 'do' => $action, ), admin_url( 'admin-post.php' ) ), 'mrnf_form_action_' . $id . '_' . $action ); } /** * Protected entry action URL. * * @param int $id Entry ID. * @param string $action Action. * @return string */ private function entry_action_url( int $id, string $action ): string { return wp_nonce_url( add_query_arg( array( 'action' => 'mrnf_entry_action', 'id' => $id, 'do' => $action, ), admin_url( 'admin-post.php' ) ), 'mrnf_entry_action_' . $id . '_' . $action ); } /** * Ensure administrator access. * * @return void */ private function authorize(): void { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'اجازه دسترسی به این بخش را ندارید.', 'mrn-forms' ), '', array( 'response' => 403 ) ); } } /** * Store a per-user flash notice. * * @param string $type Notice type. * @param string $message Message. * @return void */ private function set_notice( string $type, string $message ): void { set_transient( 'mrnf_admin_notice_' . get_current_user_id(), array( $type, $message ), MINUTE_IN_SECONDS ); } /** * Display and clear the current notice. * * @return void */ private function notice(): void { $notice = get_transient( 'mrnf_admin_notice_' . get_current_user_id() ); if ( ! $notice ) { return; } delete_transient( 'mrnf_admin_notice_' . get_current_user_id() ); ?>

page_url( $page ) ); exit; } }