generator = $generator; } /** * Hooks into the field group screen of ACF and SCF. */ public function register(): void { add_action( 'acf/field_group/admin_head', array( $this, 'add_meta_box' ) ); add_action( 'acf/field_group/admin_enqueue_scripts', array( $this, 'enqueue' ) ); } /** * Whether the current user may see the box: the capability that administers ACF or SCF. * * @return bool */ public static function user_can(): bool { $capability = function_exists( 'acf_get_setting' ) ? acf_get_setting( 'capability' ) : ''; return is_string( $capability ) && '' !== $capability && current_user_can( $capability ); } /** * Adds the box after the Fields and Settings boxes (acf/field_group/admin_head). */ public function add_meta_box(): void { if ( ! self::user_can() ) { return; } add_meta_box( 'awfc-field-code', __( 'Field Code', 'antwork-field-code' ), array( $this, 'render' ), self::POST_TYPE, 'normal', 'high' ); } /** * Scripts and styles (acf/field_group/admin_enqueue_scripts). */ public function enqueue(): void { if ( ! self::user_can() ) { return; } // WordPress's own code editor, read only; false when the user turned syntax highlighting off. $editor = wp_enqueue_code_editor( array( 'type' => 'application/x-httpd-php', 'codemirror' => array( 'readOnly' => true, 'lineNumbers' => true, 'lineWrapping' => true, 'viewportMargin' => 100000, 'indentWithTabs' => true, 'tabSize' => 4, 'styleActiveLine' => false, ), ) ); wp_enqueue_style( self::HANDLE, plugins_url( 'assets/css/field-code.css', PLUGIN_FILE ), array(), VERSION ); wp_enqueue_script( self::HANDLE, plugins_url( 'assets/js/field-code.js', PLUGIN_FILE ), array( 'wp-i18n', 'wp-a11y' ), VERSION, true ); wp_set_script_translations( self::HANDLE, 'antwork-field-code' ); $config = array( 'editor' => $editor ? $editor : false, 'supportUrl' => self::SUPPORT_URL, 'reviewUrl' => self::REVIEW_URL, 'systemInfo' => current_user_can( 'view_site_health_checks' ) ? self::system_info() : '', ); wp_add_inline_script( self::HANDLE, 'window.awfcFieldCode = ' . self::json( $config ) . ';', 'before' ); } /** * Renders the box. * * @param \WP_Post $post Field group post. */ public function render( $post ): void { echo '
'; if ( ! $post instanceof \WP_Post || 'auto-draft' === $post->post_status ) { echo '

' . esc_html__( 'Save the field group to see the code of its fields.', 'antwork-field-code' ) . '

'; return; } $group = acf_get_field_group( $post->ID ); $fields = is_array( $group ) ? acf_get_fields( $group ) : array(); $result = $this->generator->group_code( is_array( $group ) ? $group : array(), is_array( $fields ) ? $fields : array() ); if ( ! $result['fields'] ) { $line = $fields ? __( 'The fields of this group hold no value (tabs, messages...): they have no code.', 'antwork-field-code' ) : __( 'This field group has no field yet. Add fields, then save the field group.', 'antwork-field-code' ); echo '

' . esc_html( $line ) . '

'; return; } echo '
'; echo '

' . esc_html__( 'Paste in a template of your theme, inside the Loop. Shows the saved field group.', 'antwork-field-code' ) . '

'; echo '
'; echo '
'; if ( $result['uncovered_locations'] ) { $names = implode( ', ', $result['uncovered_locations'] ); $line = '' === $result['post_id'] /* translators: %s: names of location rules, such as "Options Page, User Form". */ ? sprintf( __( 'This code reads the fields of the current post. Not covered yet: %s.', 'antwork-field-code' ), $names ) /* translators: %s: names of location rules, such as "Options Page, User Form". */ : sprintf( __( 'Not covered by this code: %s.', 'antwork-field-code' ), $names ); echo '

' . esc_html( $line ) . '

'; } // One reminder per box: under the location line when Antwork Field Code Pro covers the location, otherwise // under the line of each field whose type it covers (one field shows at a time). $location_reminder = self::pro_covers_location( $result['uncovered_rules'] ); if ( $location_reminder ) { self::render_pro_reminder(); } // Without JavaScript: the list of links and the fields one under the other. The script turns them // into a list of fields on the left and the code of the chosen field on the right. echo '
'; $this->render_links( $result['fields'] ); echo '
'; foreach ( $result['fields'] as $index => $field ) { $this->render_field( (int) $index, $field, ! $location_reminder ); } echo '
'; } /** * Links to the code of each field. * * @param array $fields Fields with their code. */ private function render_links( array $fields ): void { echo ''; } /** * The code of one field, or the line that says its type is not covered. * * @param int $index Position. * @param array $field Field with its code. * @param bool $reminder Whether a field type that Antwork Field Code Pro covers gets the reminder. */ private function render_field( int $index, array $field, bool $reminder ): void { $heading = 'awfc-field-' . $index . '-title'; printf( '
', (int) $index, esc_attr( $heading ) ); echo '
'; printf( '

%2$s

%3$s%4$s', esc_attr( $heading ), esc_html( self::title( $field ) ), esc_html( $field['name'] ), esc_html( self::type_label( $field['type'] ) ) ); if ( null !== $field['code'] ) { printf( '', esc_attr( $heading ), esc_attr( self::title( $field ) ), esc_html__( 'Copy', 'antwork-field-code' ) ); } echo '
'; if ( null === $field['code'] ) { echo '

' . esc_html__( 'This field type is not covered yet.', 'antwork-field-code' ) . '

'; if ( $reminder && self::pro_covers_type( $field['type'] ) ) { self::render_pro_reminder(); } } else { printf( '
%2$s
', esc_attr( sprintf( /* translators: %s: field label. */ __( 'Code of %s', 'antwork-field-code' ), self::title( $field ) ) ), esc_html( $field['code'] ) ); } echo '
'; } /** * Whether Antwork Field Code Pro is active, with or without a license (it then says itself what is missing). * * @return bool */ public static function pro_active(): bool { return class_exists( 'Antwork\\FieldCodePro\\Plugin', false ); } /** * Whether the reminder goes under the line of a field type that is not covered. * * @param string $type Field type. * @return bool */ public static function pro_covers_type( string $type ): bool { return ! self::pro_active() && in_array( $type, self::PRO_TYPES, true ); } /** * Whether the reminder goes under the line of the locations that are not covered. * * @param string[] $rules Location rules (param) that the code does not cover. * @return bool */ public static function pro_covers_location( array $rules ): bool { return ! self::pro_active() && (bool) array_intersect( $rules, self::PRO_LOCATIONS ); } /** * The reminder: one sentence, a link opened only when clicked, and Dismiss. Hidden until the script shows it, * unless it was dismissed in this browser (localStorage): without the script, it could not be dismissed. */ private static function render_pro_reminder(): void { $link = sprintf( 'Antwork Field Code Pro %2$s', esc_url( self::PRO_URL ), esc_html__( '(opens in a new tab)', 'antwork-field-code' ) ); printf( '', wp_kses( /* translators: %s: "Antwork Field Code Pro", a link to its page. */ sprintf( esc_html__( 'Covered by %s.', 'antwork-field-code' ), $link ), array( 'a' => array( 'href' => true, 'target' => true, 'rel' => true, ), 'span' => array( 'class' => true ), ) ), esc_html__( 'Dismiss', 'antwork-field-code' ) ); } /** * Label of a field, or its name when it has none. * * @param array $field Field with its code. * @return string */ private static function title( array $field ): string { $label = trim( wp_strip_all_tags( $field['label'] ) ); return '' !== $label ? $label : $field['name']; } /** * Name of a field type, as ACF or SCF shows it; for a type they do not register (Gallery in ACF free, * a type of a plugin that is turned off), its name in words. * * @param string $type Field type. * @return string */ private static function type_label( string $type ): string { if ( function_exists( 'acf_get_field_type' ) ) { $object = acf_get_field_type( $type ); if ( is_object( $object ) && isset( $object->label ) && is_string( $object->label ) && '' !== $object->label ) { return $object->label; } } return ucwords( str_replace( array( '_', '-' ), ' ', $type ) ); } /** * Versions to paste in a support request. Built here, copied by the browser: nothing is sent. * * @return string */ public static function system_info(): string { $lines = array( 'WordPress: ' . get_bloginfo( 'version' ), 'PHP: ' . PHP_VERSION, ); if ( defined( 'ACF_VERSION' ) ) { $name = function_exists( 'acf_get_setting' ) ? (string) acf_get_setting( 'name' ) : ''; $name = '' !== $name ? $name : 'Advanced Custom Fields'; $name .= defined( 'ACF_PRO' ) && ACF_PRO ? ' PRO' : ''; $lines[] = $name . ': ' . ACF_VERSION; } $lines[] = 'Antwork Field Code: ' . VERSION; return implode( "\n", $lines ); } /** * JSON that is safe inside a