item_settings = $item_settings; $this->layouts_settings_storage = $layouts_settings_storage; $this->provider_cluster = $provider_cluster; $this->layouts_cpt_save_actions = $layouts_cpt_save_actions; $this->layout_factory = $layout_factory; $this->data_vendor = $data_vendor; $this->layout_shortcode = $layout_shortcode; $this->layouts_cpt = $layouts_cpt; } abstract protected function get_vendor_post_type(): string; /** * @return mixed[] */ abstract protected function get_group_fields( WP_Post $wp_post ): array; /** * @param array $field */ abstract protected function fill_field_id_and_type( array $field, string &$field_id, string &$field_type ): void; protected function get_provider_cluster(): Field_Provider_Cluster { return $this->provider_cluster; } protected function get_block_description( Layout_Settings $layout_settings ): string { return sprintf( '%s (%s, id = %s).', $layout_settings->description, __( 'Advanced View', 'acf-views' ), $layout_settings->get_unique_id( true ), ); } protected function get_block_id( Layout_Settings $layout_settings ): string { return $layout_settings->is_gutenberg_block_with_digital_id ? sprintf( 'acf-views-block-%s', $layout_settings->getSource() ) : sprintf( 'acf-view-%s', $layout_settings->get_unique_id( true ) ); } protected function getBlockCategory(): string { return __( 'Advanced Views', 'acf-views' ); } /** * @param array|null $local_data */ protected function render_view( Layout_Settings $layout_settings, int $post_id, ?array $local_data = null ): void { $source = new Source(); $source->set_id( $post_id ); $source->set_is_block( true ); $source->set_user_id( get_current_user_id() ); ob_start(); $this->layout_factory->make_and_print_html( $source, $layout_settings->get_unique_id(), $post_id, '', array(), $local_data ); $html = (string) ob_get_clean(); // @phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $this->layout_shortcode->maybe_add_quick_link_and_shadow_css( $html, $layout_settings->get_unique_id(), array(), true ); } protected function print_link_with_js_hover( string $url, string $title ): void { echo ' esc_url( $url ), 'target' => '_blank', 'style' => 'transition: all .3s ease;', 'onMouseOver' => "this.style.filter='brightness(30%)'", 'onMouseOut' => "this.style.filter='brightness(100%)'", ); foreach ( $attrs as $key => $value ) { printf( ' %s="%s"', esc_html( $key ), esc_attr( $value ) ); } printf( '>%s', esc_html( $title ) ); } /** * @param mixed[] $field * @param string[] $supported_field_types */ protected function add_item_to_view( string $group_id, array $field, Layout_Settings $layout_settings, array $supported_field_types ): ?Item_Settings { $field_id = ''; $field_type = ''; $this->fill_field_id_and_type( $field, $field_id, $field_type ); if ( ! in_array( $field_type, $supported_field_types, true ) ) { return null; } $item = $this->item_settings->getDeepClone(); // we could use the group field on the level, but it less clear for beginners, // as they may wonder how to add fields from other groups, like WP. $item->group = $group_id; $item->field->key = $item->field::create_field_key( $group_id, $field_id ); $layout_settings->items[] = $item; return $item; } /** * @param array $link_attrs */ protected function print_add_new_link( int $from, string $label_suffix = '', array $link_attrs = array() ): void { $link_attrs = array_merge( array( 'target' => '_blank', 'class' => 'button', 'style' => 'min-height: 0;line-height: 1.2;padding: 3px 7px;font-size:11px;height:auto;transition:all .3s ease;', 'onmouseover' => "this.style.color='#044767'", 'onmouseout' => "this.style.color='#0783BE'", 'icon_class' => '', 'icon_style' => '', ), $link_attrs ); $label = __( 'Add new', 'acf-views' ) . $label_suffix; $url = add_query_arg( array( 'post_type' => Hard_Layout_Cpt::cpt_name(), self::ARGUMENT_FROM => $from, '_wpnonce' => wp_create_nonce( self::NONCE_ADD_NEW ), ), admin_url( '/post-new.php' ) ); printf( ' $value ) { printf( ' %s="%s"', esc_html( $attr ), esc_attr( $value ) ); } echo '>'; if ( '' !== $link_attrs['icon_class'] ) { printf( '', esc_html( $link_attrs['icon_class'] ), esc_attr( $link_attrs['icon_style'] ) ); } echo esc_html( $label ); echo ''; } /** * @param Layout_Settings[] $related_views * @param array $add_new_link_args */ protected function print_related_acf_views( ?WP_Post $wp_post, bool $is_list_look = false, array $related_views = array(), array $add_new_link_args = array() ): void { $related_views = null !== $wp_post ? $this->layouts_settings_storage->get_all_with_meta_group_in_use( $this->data_vendor->get_group_key( $wp_post->post_name ) ) : $related_views; $label = array() !== $related_views ? // translators: %s is the plural name of the CPT. __( 'Assigned to %s:', 'acf-views' ) . ' ' : // translators: %s is the plural name of the CPT. __( 'Not assigned to any %s.', 'acf-views' ); $label = sprintf( $label, $this->layouts_cpt->labels()->plural_name() ); if ( ! $is_list_look ) { echo esc_html( $label ); } $last_index = count( $related_views ) - 1; $counter = 0; foreach ( $related_views as $related_view ) { $this->print_link_with_js_hover( $related_view->get_edit_post_link(), $related_view->title, ); if ( $counter !== $last_index ) { echo ', '; } ++$counter; } // ignore on the creation page + // if post is missing (Pods). if ( null === $wp_post || 'publish' !== $wp_post->post_status ) { return; } if ( array() === $related_views && $is_list_look ) { echo ''; } echo '

'; $this->print_add_new_link( $wp_post->ID, '', $add_new_link_args ); } /** * @param Layout_Settings[] $related_views * * @throws Exception */ protected function update_markup_preview( array $related_views ): void { foreach ( $related_views as $related_view_data ) { // update the markup preview in all the cases (even if View has custom, Preview must be fresh for copy/paste). $this->layouts_cpt_save_actions->update_markup( $related_view_data ); $this->layouts_settings_storage->save( $related_view_data ); } } /** * @param Layout_Settings[] $related_views * * @return string[] * @throws Exception */ protected function get_related_view_links_with_invalid_custom_markup( array $related_views ): array { $views_with_invalid_custom_markup = array(); foreach ( $related_views as $related_view_data ) { // update the markup preview in all the cases (even if View has custom, Preview must be fresh for copy/paste) // also, it's necessary to update the markupPreview before the validation // as the validation uses the markupPreview as 'canonical' for the 'array' type validation. $this->layouts_cpt_save_actions->update_markup( $related_view_data ); $this->layouts_settings_storage->save( $related_view_data ); $custom_markup = trim( $related_view_data->custom_markup ); if ( '' === $custom_markup ) { continue; } $view = $this->layout_factory->make( new Source(), $related_view_data->get_unique_id(), 0 ); $is_with_error = '' !== $view->get_markup_validation_error(); if ( ! $is_with_error ) { continue; } $views_with_invalid_custom_markup[] = sprintf( "%s", $related_view_data->get_edit_post_link(), $related_view_data->title ); } return $views_with_invalid_custom_markup; } protected function get_tab_label(): string { return __( 'Advanced Views', 'acf-views' ); } /** * @param mixed[] $field */ protected function get_group_key_by_from_post( WP_Post $wp_post, array $field ): string { return $this->data_vendor->get_group_key( $wp_post->post_name ); } public function get_views_data_storage(): Layout_Settings_Storage { return $this->layouts_settings_storage; } /** * @param array $columns * * @return array */ public function add_related_views_column_to_list( array $columns ): array { return array_merge( $columns, array( 'relatedAcfViews' => __( 'Assigned to Layout', 'acf-views' ), ) ); } public function maybe_create_view_for_group(): void { self::add_action( 'current_screen', function (): void { $screen = get_current_screen(); if ( null === $screen ) { return; } $from = Query_Arguments::get_int_for_admin_action( self::ARGUMENT_FROM, self::NONCE_ADD_NEW ); $from_post = 0 !== $from ? get_post( $from ) : null; $is_add_screen = 'post' === $screen->base && 'add' === $screen->action; if ( Hard_Layout_Cpt::cpt_name() !== $screen->post_type || false === $is_add_screen || null === $from_post || $this->get_vendor_post_type() !== $from_post->post_type || 'publish' !== $from_post->post_status || ! Avf_User::can_manage() ) { return; } $layout_settings = $this->layouts_settings_storage->create_new( 'publish', $from_post->post_title ); if ( null === $layout_settings ) { return; } $supported_field_types = $this->provider_cluster->get_supported_field_types( $this->get_vendor_name() ); $group_fields = $this->get_group_fields( $from_post ); $group_key = ''; $layout_settings->title = $from_post->post_title; $this->set_defaults_from_settings( $layout_settings ); // get group field key if at least one field is present // (e.g. Pods requires a field data to get the '_pod_name' part). if ( count( $group_fields ) > 0 ) { $group_key = $this->get_group_key_by_from_post( $from_post, arr( $group_fields[0] ) ); // Leave global group filter empty, so beginners can easily mix fields from different groups. // $view_data->group = $group_key;. } foreach ( $group_fields as $field ) { $field = arr( $field ); $this->add_item_to_view( $group_key, $field, $layout_settings, $supported_field_types ); } // it'll save the data above too. $this->layouts_cpt_save_actions->perform_save_actions( $layout_settings->get_post_id() ); wp_safe_redirect( $layout_settings->get_edit_post_link( 'redirect' ) ); exit; } ); } public function print_related_views_column( string $column, int $post_id ): void { if ( 'relatedAcfViews' !== $column ) { return; } $post = get_post( $post_id ); if ( null === $post ) { return; } $this->print_related_acf_views( $post, true ); } public function validate_related_views_on_group_change(): void { // with '20', to make sure it's after ACF. self::add_filter( 'post_updated_messages', function ( array $messages ) { if ( ! key_exists( $this->get_vendor_post_type(), $messages ) ) { return $messages; } global $post; if ( null === $post || $this->get_vendor_post_type() !== $post->post_type || 'publish' !== $post->post_status ) { return $messages; } $related_views = $this->layouts_settings_storage->get_all_with_meta_group_in_use( $this->data_vendor->get_group_key( $post->post_name ) ); $this->update_markup_preview( $related_views ); $related_view_links_with_invalid_custom_markup = $this->get_related_view_links_with_invalid_custom_markup( $related_views ); if ( array() === $related_view_links_with_invalid_custom_markup ) { return $messages; } $extra = sprintf( "

%s %s:

", count( $related_view_links_with_invalid_custom_markup ), __( 'Views associated with this group contain invalid Custom Markup', 'acf-views' ) ); $extra .= implode( '
', $related_view_links_with_invalid_custom_markup ); $messages[ $this->get_vendor_post_type() ][1] .= $extra; return $messages; }, 20 ); } public function add_column_to_list_table(): void { // higher priority, to run after ACF's listener (they don't use 'merge'). self::add_filter( sprintf( 'manage_%s_posts_columns', $this->get_vendor_post_type() ), array( $this, 'add_related_views_column_to_list' ), 20 ); self::add_action( sprintf( 'manage_%s_posts_custom_column', $this->get_vendor_post_type() ), array( $this, 'print_related_views_column' ), 10, 2 ); } /** * @param Layout_Settings[] $view_data_items * * @return void */ public function signup_gutenberg_blocks( array $view_data_items ): void { // it's a stub. Will be overridden by specific vendors in the Pro version. } }