HEX
Server: Apache/2.4.63 (Unix)
System: Linux TOMS-220NAS 4.4.302+ #86009 SMP Wed Nov 26 18:19:17 CST 2025 x86_64
User: flavio87 (1026)
PHP: 8.3.27
Disabled: NONE
Upload Files
File: /volume1/web/Marchisa-Toman/wp-content/plugins/meta-box-builder/src/AdminColumns.php
<?php
namespace MBB;

use MBB\Helpers\Data;
use MBB\Helpers\Template;
use MetaBox\Support\Arr;

class AdminColumns {
	/**
	 * Current view
	 *
	 * @var string $view
	 */
	protected $view;

	protected $post_type = 'meta-box';

	public function __construct() {
		add_action( 'admin_print_styles-edit.php', [ $this, 'enqueue' ] );
		add_filter( 'manage_meta-box_posts_columns', [ $this, 'add_columns' ] );
		add_action( 'manage_meta-box_posts_custom_column', [ $this, 'show_column' ], 10, 2 );
		add_filter( 'views_edit-meta-box', [ $this, 'admin_table_views' ], 10, 1 );
		add_filter( 'bulk_actions-edit-meta-box', [ $this, 'admin_table_bulk_actions' ], 10, 1 );
		add_action( 'current_screen', [ $this, 'current_screen' ] );
		add_action( 'admin_footer', [ Template::class, 'render_diff_dialog' ] );
		add_action( 'admin_notices', [ $this, 'admin_notices' ] );

		// Delete posts should delete the json file as well.
		add_action( 'wp_trash_post', [ $this, 'delete_json' ], 10, 1 );
		add_action( 'before_delete_post', [ $this, 'delete_json' ], 10, 1 );

		add_filter( 'wp_untrash_post_status', [ $this, 'set_post_status' ], 10, 2 );
		// Restore posts should restore the json file as well.
		add_action( 'untrashed_post', [ $this, 'restore_json' ], 10, 1 );

		add_action( 'transition_post_status', [ $this, 'handle_draft_to_publish' ], 10, 3 );
	}

	public function handle_draft_to_publish( $new_status, $old_status, $post ) {
		// Bail if LocalJson is not enabled.
		if ( ! LocalJson::is_enabled() ) {
			return;
		}

		if ( $post->post_type !== $this->post_type ) {
			return;
		}

		if ( $new_status === 'publish' && $old_status === 'draft' ) {
			// When switching from 'draft' to 'publish', the earlier meta box does not contains id
			// (since draft posts don't have post_name property).
			// So, we need to set the id for the meta box
			$meta_box = get_post_meta( $post->ID, 'meta_box', true );

			if ( ! is_array( $meta_box ) ) {
				return;
			}

			if ( ! isset( $meta_box['id'] ) ) {
				$meta_box['id'] = $post->post_name;
				update_post_meta( $post->ID, 'meta_box', $meta_box );
			}

			// Publish the json file.
			LocalJson::use_database( [
				'post_id' => $post->ID,
			] );
		}
	}

	public function set_post_status( $new_status, $post_id ) {
		// Bail if LocalJson is not enabled.
		if ( ! LocalJson::is_enabled() ) {
			return $new_status;
		}

		if ( $new_status === 'draft' ) {
			$post = get_post( $post_id );

			if ( $post->post_type !== $this->post_type ) {
				return $new_status;
			}

			return 'publish';
		}

		return $new_status;
	}

	public function delete_json( $post_id ) {
		$post = get_post( $post_id );
		if ( ! $post ) {
			return;
		}

		$json = JsonService::get_json( [
			'post_id' => $post_id,
		] );

		if ( empty( $json ) ) {
			return;
		}

		$meta_box_id = array_key_first( $json );
		$file_path   = $json[ $meta_box_id ]['file'];

		if ( ! file_exists( $file_path ) ) {
			return;
		}

		unlink( $file_path );
	}

	public function restore_json( $post_id ): bool {
		$post = get_post( $post_id );
		if ( ! $post ) {
			return false;
		}

		return LocalJson::use_database( [
			'post_id' => $post_id,
		] );
	}

	public function admin_notices(): void {
		if ( get_current_screen()->id !== 'edit-meta-box' ) {
			return;
		}

		// Don't show other notices.
		remove_all_actions( 'admin_notices' );

		if ( ! LocalJson::is_enabled() ) {
			return;
		}

		$custom_admin_notice = $_GET['status'] ?? '';

		$messages = [
			'imported'      => [
				'status'  => 'success',
				'message' => __( 'Imported successfully', 'meta-box-builder' ),
			],
			'import-failed' => [
				'status'  => 'error',
				'message' => __( 'Import failed', 'meta-box-builder' ),
			],
		];

		if ( ! isset( $messages[ $custom_admin_notice ] ) ) {
			return;
		}
		?>
		<div class="notice notice-<?php esc_attr_e( $messages[ $custom_admin_notice ]['status'] ) ?> is-dismissible">
			<p><?php esc_html_e( $messages[ $custom_admin_notice ]['message'] ) ?></p>
		</div>
		<?php
	}

	public function current_screen() {
		if ( ! LocalJson::is_enabled() ) {
			return;
		}
		$screen = get_current_screen();

		if ( $screen->id !== 'edit-meta-box' ) {
			return;
		}

		$this->view      = $_GET['post_status'] ?? ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- used as intval to return a page.
		$this->post_type = $_GET['post_type'] ?? 'meta-box'; //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- used as intval to return a page.

		$this->check_sync();

		if ( ! $this->is_status( 'sync' ) ) {
			return;
		}

		add_action( 'admin_footer', [ $this, 'render_sync_template' ], 1 );
	}

	public function check_sync() {
		if ( ! LocalJson::is_enabled() ) {
			return;
		}
		$action = $_GET['action'] ?? ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- used as intval to return a page.

		if ( $action !== 'mbb-sync' ) {
			return;
		}

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( __( 'Not allowed', 'meta-box-builder' ) );
		}

		$id = $_GET['id'] ?? $_GET['post'] ?? ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- used as intval to return a page.

		if ( empty( $id ) ) {
			wp_die( __( 'Not found', 'meta-box-builder' ) );
		}

		// Bulk actions
		if ( ! is_array( $id ) ) {
			return;
		}

		$json = JsonService::get_json();

		$json = array_filter( $json, function ( $item, $key ) use ( $id ) {
			return in_array( $key, $id, true );
		}, ARRAY_FILTER_USE_BOTH );

		LocalJson::import_many( $json );

		wp_safe_redirect( admin_url( 'edit.php?post_type=meta-box&message=import-success' ) );
		exit;
	}

	public function render_sync_template() {
		global $wp_list_table;
		// Get table columns.
		$columns = $wp_list_table->get_columns();
		$hidden  = get_hidden_columns( $wp_list_table->screen );
		$json    = JsonService::get_json();

		// Filter where local is not null
		// and its should not imported yet.
		$json = array_filter( $json, function ( $item ) {
			return isset( $item['local'] ) && $item['is_newer'] !== 0;
		} );
		?>
		<template id="mb-sync-list">
			<tbody>
				<?php foreach ( $json as $id => $data ) : ?>
					<tr>
						<?php
						foreach ( $columns as $name => $label ) :
							$tag     = $name === 'cb' ? 'th' : 'td';
							$classes = [ $name, "column-$name" ];

							if ( $name === 'cb' ) {
								$classes[] = 'check-column';
								$label     = '';
							}

							if ( $name === 'title' ) {
								$classes[] = 'column-primary';
							}

							if ( in_array( $name, $hidden ) ) {
								$classes[] = ' hidden';
							}

							echo '<' . $tag . ' class="' . esc_attr( implode( ' ', $classes ) ) . '" data-colname="' . esc_attr( $label ) . '">';

							switch ( $name ) {
								case 'cb':
									echo '<label for="cb-select-' . esc_attr( $id ) . '" class="screen-reader-text">';
									/* translators: %s: field group title */
									echo esc_html( sprintf( __( 'Select %s', 'meta-box-builder' ), $data['local']['title'] ?? '' ) );
									echo '</label>';
									echo '<input id="cb-select-' . esc_attr( $id ) . '" type="checkbox" value="' . esc_attr( $id ) . '" name="post[]">';
									break;

								case 'title':
									echo esc_html( $data['local_minimized']['title'] );
									break;

								case 'for':
									$this->show_for( $data['local_minimized'] );
									break;

								case 'path':
									$this->show_path( $id );
									break;

								case 'sync_status':
									$this->show_sync_status( $id );
									break;
							}

							echo "</$tag>";
						endforeach;
						?>
					</tr>
				<?php endforeach; ?>
			</tbody>
		</template>

		<script>
			document.addEventListener( 'DOMContentLoaded', () => {
				const template = document.querySelector( '#mb-sync-list' );
				const tbody = template.content.querySelector( 'tbody' );
				document.querySelector( '.wp-list-table tbody' ).replaceWith( tbody );
			} );
		</script>
		<?php
	}

	private function is_status( string $status ): bool {
		return isset( $_GET['post_status'] ) && $_GET['post_status'] === $status; //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- used as intval to return a page.
	}

	public function admin_table_bulk_actions( $actions ) {
		if ( $this->is_status( 'sync' ) ) {
			unset( $actions['edit'] );
			unset( $actions['trash'] );

			$actions['mbb-sync'] = __( 'Sync changes', 'meta-box-builder' );
		}

		return $actions;
	}

	public function admin_table_views( $views ) {
		global $wp_list_table, $wp_query;

		$json = JsonService::get_json();

		$json = array_filter( $json, function ( $item ) {
			return isset( $item['local'] ) && $item['is_newer'] !== 0;
		} );

		$count = count( $json );

		if ( $count ) {
			$url = add_query_arg( [
				'post_status' => 'sync',
			] );

			$views['sync'] = sprintf(
				'<a %s href="%s">%s <span class="count">(%s)</span></a>',
				$this->is_status( 'sync' ) ? 'class="current"' : '',
				$url,
				esc_html( __( 'Sync available', 'meta-box-builder' ) ),
				$count
			);
		}

		if ( $this->view === 'sync' ) {
			$wp_list_table->set_pagination_args( [
				'total_items' => $count,
				'total_pages' => 1,
				'per_page'    => $count,
			] );
			$wp_query->post_count = 1; // At least one post is needed to render bulk drop-down.
		}

		return $views;
	}

	public function enqueue() {
		if ( ! in_array( get_current_screen()->id, [ 'edit-meta-box', 'edit-mb-relationship', 'edit-mb-settings-page' ], true ) ) {
			return;
		}

		wp_enqueue_style( 'mbb-list', MBB_URL . 'assets/css/list.css', [], time() );
		wp_enqueue_script( 'mbb-list', MBB_URL . 'assets/js/list.js', [ 'jquery' ], MBB_VER, true );
		wp_enqueue_script( 'mbb-dialog', MBB_URL . 'assets/js/dialog.js', [ 'jquery', 'wp-api-fetch' ], MBB_VER, true );
		wp_enqueue_style( 'mbb-dialog', MBB_URL . 'assets/css/dialog.css', [], MBB_VER );
		wp_localize_script( 'mbb-dialog', 'MBBDialog', [
			'export'         => esc_html__( 'Export', 'meta-box-builder' ),
			'import'         => esc_html__( 'Import', 'meta-box-builder' ),
			'not_imported'   => esc_html__( 'Not Imported', 'meta-box-builder' ),
			'error'          => esc_html__( 'Error!', 'meta-box-builder' ),
			'synced'         => esc_html__( 'Synced', 'meta-box-builder' ),
			'syncing'        => esc_html__( 'Syncing...', 'meta-box-builder' ),
			'newer'          => esc_html__( '(newer)', 'meta-box-builder' ),
			'sync_available' => esc_html__( 'Sync available', 'meta-box-builder' ),
		] );

		if ( Data::is_extension_active( 'mb-frontend-submission' ) ) {
			wp_register_script( 'popper', 'https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js', [], '2.11.8', true );
			wp_enqueue_script( 'tippy', 'https://cdn.jsdelivr.net/npm/tippy.js@6.3.7/dist/tippy-bundle.umd.min.js', [ 'popper' ], '6.3.7', true );
			wp_add_inline_script( 'tippy', 'tippy( ".mbb-tooltip", {placement: "top", arrow: true, animation: "fade"} );' );
		}
	}

	public function add_columns( $columns ) {
		$new_columns = [
			'for'      => __( 'Show For', 'meta-box-builder' ),
			'location' => __( 'Location', 'meta-box-builder' ),
		];

		if ( Data::is_extension_active( 'mb-frontend-submission' ) && ! $this->is_status( 'sync' ) ) {
			$new_columns['shortcode'] = __( 'Shortcode', 'meta-box-builder' ) . Data::tooltip( __( 'Embed the field group in the front end for users to submit posts.', 'meta-box-builder' ) );
		}

		if ( LocalJson::is_enabled() && ! $this->is_status( 'trash' ) ) {
			$new_columns['path']        = __( 'Path', 'meta-box-builder' );
			$new_columns['sync_status'] = __( 'Sync status', 'meta-box-builder' ) . Data::tooltip( __( 'You must set the modified time to a Unix timestamp for it to display correctly.', 'meta-box-builder' ) );
		}

		$columns = array_slice( $columns, 0, 2, true ) + $new_columns + array_slice( $columns, 2, null, true );

		if ( $this->is_status( 'sync' ) ) {
			unset( $columns['location'] );
			unset( $columns['date'] );
		}

		return $columns;
	}

	public function show_column( $column, $post_id ) {
		if ( ! in_array( $column, [ 'for', 'location', 'shortcode', 'path', 'sync_status' ], true ) ) {
			return;
		}

		$post_name = $post_id;
		if ( in_array( $column, [ 'sync_status', 'path' ], true ) && LocalJson::is_enabled() ) {
			if ( is_numeric( $post_id ) ) {
				$post      = get_post( $post_id );
				$post_name = $post->post_name;
			}

			call_user_func( [ $this, "show_$column" ], $post_name );
			return;
		}

		$data = get_post_meta( get_the_ID(), 'settings', true );

		if ( ! is_array( $data ) ) {
			return;
		}

		$this->{"show_$column"}( $data );
	}

	private function show_sync_status( string $meta_box_id ) {
		if ( ! LocalJson::is_enabled() ) {
			return;
		}

		if ( $meta_box_id === null ) {
			return;
		}

		$json = JsonService::get_json( [
			'id' => $meta_box_id,
		] );

		if ( empty( $json ) || ! is_array( $json ) ) {
			return;
		}

		$sync_data = $json[ $meta_box_id ];

		// Empty sync data means no related json file.
		if ( empty( $sync_data ) ) {
			return;
		}

		$available_statuses = [
			'error_file_permission' => __( 'Error: Not writable', 'meta-box-builder' ),
			'sync_available'        => __( 'Sync available', 'meta-box-builder' ),
			'no_json'               => __( 'No JSON available', 'meta-box-builder' ),
			'synced'                => __( 'Synced', 'meta-box-builder' ),
		];

		$status = 'sync_available';

		if ( $sync_data['is_newer'] === 0 ) {
			$status = 'synced';
		}
		if ( ! $sync_data['is_writable'] ) {
			$status = 'error_file_permission';
		}
		if ( $sync_data['local'] === null ) {
			$status = 'no_json';
		}
		?>
		<span class="mbb-label" data-status="<?php esc_attr_e( $status ) ?>" data-for-id="<?php esc_attr_e( $meta_box_id ) ?>">
			<?php esc_html_e( $available_statuses[ $status ] ) ?>
		</span>

		<?php
		if ( $status !== 'sync_available' ) {
			return;
		}
		?>
		<div class="row-actions">
			<span class="sync">
				<a class="button-sync" data-use="json" data-id="<?php esc_html_e( $meta_box_id ) ?>" href="javascript:;">
					<?= esc_html__( 'Sync', 'meta-box-builder' ); ?>
				</a>
			</span>
			|
			<span class="review">
				<a href="javascript:;" role="button" data-dialog="<?php esc_attr_e( $meta_box_id ) ?>">
					<?= esc_html__( 'Review', 'meta-box-builder' ); ?>
				</a>
			</span>
		</div>
		<?php
	}

	private function show_for( $data ): void {
		$object_type = Arr::get( $data, 'object_type', 'post' );

		$labels = [
			'user'    => __( 'Users', 'meta-box-builder' ),
			'comment' => __( 'Comments', 'meta-box-builder' ),
			'setting' => __( 'Settings Pages', 'meta-box-builder' ),
			'post'    => __( 'Posts', 'meta-box-builder' ),
			'term'    => __( 'Taxonomies', 'meta-box-builder' ),
			'block'   => __( 'Blocks', 'meta-box-builder' ),
		];

		esc_html_e( $labels[ $object_type ] ?? '' );
	}

	/**
	 * Display human friendly file location to display in the column.
	 *
	 * @param string $file
	 * @return string
	 */
	private function format_file_location( string $file ): string {
		// Get the relative path of the file.
		$active_theme = get_template_directory();
		$plugins_path = WP_PLUGIN_DIR;

		$icon     = 'WordPress';
		$sub_path = str_replace( ABSPATH, '', $file );

		if ( str_contains( $file, $active_theme ) ) {
			$icon     = 'admin-appearance';
			$sub_path = str_replace( $active_theme, '', $file );
		}

		if ( str_contains( $file, $plugins_path ) ) {
			$icon     = 'admin-plugins';
			$sub_path = str_replace( $plugins_path, '', $file );
		}

		$icon     = esc_attr( $icon );
		$sub_path = esc_html( $sub_path );

		return "<span class=\"dashicons dashicons-{$icon}\"></span> <span class=\"mbb-sub-path\">{$sub_path}</span>";
	}

	public function show_path( string $meta_box_id ): void {
		$json = JsonService::get_json( [
			'id'        => $meta_box_id,
			'post_type' => $this->post_type,
		] );

		$data = reset( $json );

		if ( ! is_array( $data ) || ! isset( $data['file'] ) || ! file_exists( $data['file'] ) ) {
			echo esc_html__( 'File not found', 'meta-box-builder' );
			return;
		}

		echo $this->format_file_location( $data['file'] );
	}

	private function show_location( array $data ): void {
		$object_type = Arr::get( $data, 'object_type', 'post' );

		switch ( $object_type ) {
			case 'user':
				esc_html_e( 'All Users', 'meta-box-builder' );
				break;
			case 'comment':
				esc_html_e( 'All Comments', 'meta-box-builder' );
				break;
			case 'setting':
				$settings_pages = Data::get_setting_pages();
				$settings_pages = wp_list_pluck( $settings_pages, 'title', 'id' );
				$ids            = Arr::get( $data, 'settings_pages', [] );
				$saved          = array_intersect_key( $settings_pages, array_flip( $ids ) );
				echo wp_kses_post( implode( '<br>', $saved ) );
				break;
			case 'post':
				echo wp_kses_post( implode( '<br>', array_filter( array_map( function ( $post_type ) {
					$post_type_object = get_post_type_object( $post_type );
					return $post_type_object ? $post_type_object->labels->singular_name : '';
				}, Arr::get( $data, 'post_types', [ 'post' ] ) ) ) ) );
				break;
			case 'term':
				echo wp_kses_post( implode( '<br>', array_filter( array_map( function ( $taxonomy ) {
					$taxonomy_object = get_taxonomy( $taxonomy );
					return $taxonomy_object ? $taxonomy_object->labels->singular_name : '';
				}, Arr::get( $data, 'taxonomies', [] ) ) ) ) );
				break;
			case 'block':
				if ( isset( $data['block_json'] ) && isset( $data['block_json']['path'] ) ) {
					echo esc_html( $data['block_json']['path'] );
				}
				break;
		}
	}

	private function show_shortcode(): void {
		global $post;

		$shortcode = "[mb_frontend_form id='{$post->post_name}' post_fields='title,content']";
		echo '<input type="text" readonly value="' . esc_attr( $shortcode ) . '" onclick="this.select()">';
	}
}