# metasync/2.7.1/public/class-metasync-public.php

Search Atlas SEO – OTTO AI SEO Automation for WordPress, version 2.7.1. 432 lines.

- Page: https://pluginprobe.com/plugins/metasync/2.7.1/code/public/class-metasync-public.php
- Raw: https://pluginprobe.com/plugins/metasync/2.7.1/raw/public/class-metasync-public.php
- Modified: 2026-09-25T22:31:34+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/metasync/2.7.1/code/public/class-metasync-public.php#L10-L20`.

```php
<?php
// If this file is called directly, abort.
if (!defined('ABSPATH')) {
	exit;
}


/**
 * The public-facing functionality of the plugin.
 *
 * Thin coordinator that instantiates Metasync_Rest_Api and Metasync_Seo_Output
 * and retains only asset enqueue methods, plugin links, and shortcode init.
 *
 * @link       https://searchatlas.com
 * @since      1.0.0
 *
 * @package    Metasync
 * @subpackage Metasync/public
 */

/**
 * The public-facing functionality of the plugin.
 *
 * Defines the plugin name, version, and two examples hooks for how to
 * enqueue the public-facing stylesheet and JavaScript.
 *
 * @package    Metasync
 * @subpackage Metasync/public
 * @author     Engineering Team <support@searchatlas.com>
 */

class Metasync_Public
{

	/**
	 * The ID of this plugin.
	 *
	 * @since    1.0.0
	 * @access   private
	 * @var      string    $plugin_name    The ID of this plugin.
	 */
	private $plugin_name;

	/**
	 * The version of this plugin.
	 *
	 * @since    1.0.0
	 * @access   private
	 * @var      string    $version    The current version of this plugin.
	 */
	private $version;

	/**
	 * REST API handler instance.
	 *
	 * @since    1.0.0
	 * @access   private
	 * @var      Metasync_Rest_Api    $rest_api
	 */
	private $rest_api;

	/**
	 * SEO Output handler instance.
	 *
	 * @since    1.0.0
	 * @access   private
	 * @var      Metasync_Seo_Output    $seo_output
	 */
	private $seo_output;

	/**
	 * Initialize the class and set its properties.
	 *
	 * @since	1.0.0
	 * @param	string	$plugin_name	The name of the plugin.
	 * @param	string	$version		The version of this plugin.
	 */
	public function __construct($plugin_name, $version)
	{
		$this->plugin_name = $plugin_name;
		$this->version = $version;

		$this->rest_api = new Metasync_Rest_Api($plugin_name, $version);
		$this->seo_output = new Metasync_Seo_Output($plugin_name, $version);
	}

	/**
	 * Get the REST API handler instance.
	 *
	 * @return Metasync_Rest_Api
	 */
	public function get_rest_api()
	{
		return $this->rest_api;
	}

	/**
	 * Get the SEO Output handler instance.
	 *
	 * @return Metasync_Seo_Output
	 */
	public function get_seo_output()
	{
		return $this->seo_output;
	}

	/**
	 * Register the stylesheets for the public-facing side of the site.
	 *
	 * @since    1.0.0
	 */
	public function enqueue_styles()
	{

		/**
		 * This function is provided for demonstration purposes only.
		 *
		 * An instance of this class should be passed to the run() function
		 * defined in Metasync_Loader as all of the hooks are defined
		 * in that particular class.
		 *
		 * The Metasync_Loader will then create the relationship
		 * between the defined hooks and the functions defined in this
		 * class.
		 */
		$enabled_plugin_css = Metasync::get_option('general')['enabled_plugin_css'] ?? '';
		if($enabled_plugin_css!=="default" && $enabled_plugin_css!=='' ){
			wp_enqueue_style($this->plugin_name, plugin_dir_url(__FILE__) . 'css/metasync-public.css', array(), $this->version, 'all');
		}
	}

	/**
	 * Register the JavaScript for the public-facing side of the site.
	 *
	 * @since    1.0.0
	 */
	public function enqueue_scripts()
	{

		/**
		 * This function is provided for demonstration purposes only.
		 *
		 * An instance of this class should be passed to the run() function
		 * defined in Metasync_Loader as all of the hooks are defined
		 * in that particular class.
		 *
		 * The Metasync_Loader will then create the relationship
		 * between the defined hooks and the functions defined in this
		 * class.
		 */

		# Get the Otto Pixel UUID from plugin settings.
		$otto_uuid = Metasync::get_option('general')['otto_pixel_uuid'] ?? '';

		# The tracker self-exits when window.saOttoData is absent, and saOttoData is
		# only localized when a UUID is configured — so without a UUID the request
		# ships a script that can never do anything. Skip it entirely instead.
		# OTTO SSR is always enabled by default when UUID is set.
		if (!empty($otto_uuid)) {

			# Enqueue the JavaScript file 'otto-tracker.js' with defer for better performance
			# Load in footer (true) and add defer attribute to prevent render blocking
			# No jQuery dependency: the tracker is plain ES5/ES6 and uses no $.
			wp_enqueue_script($this->plugin_name . '-tracker', plugin_dir_url(__FILE__) . 'js/otto-tracker.min.js', array(), $this->version, true);

			# Get the current full page URL safely.
			$page_url = esc_url(home_url(add_query_arg([], $_SERVER['REQUEST_URI'])));

			# Pass PHP data to the JavaScript file using wp_localize_script.
			wp_localize_script($this->plugin_name . '-tracker', 'saOttoData', [
				'otto_uuid' => $otto_uuid,
				'page_url'  => $page_url,
				'context'   => null,
				'enable_metadesc' => true, // Always enabled by default
			]);
		}

		# metasync-public.js drives nothing but the [accordion_metasync] shortcode,
		# so it only needs to load on a page that actually renders that shortcode.
		if ($this->has_accordion_shortcode()) {
			wp_enqueue_script($this->plugin_name, plugin_dir_url(__FILE__) . 'js/metasync-public.js', array(), $this->version, true);

			# CSS and JS gate on the same predicate so a placement the scan misses degrades to visible-but-unstyled content, never hidden content.
			wp_enqueue_style($this->plugin_name . '-accordion', plugin_dir_url(__FILE__) . 'css/metasync-accordion.css', array(), $this->version, 'all');
		}
	}

	/**
	 * Whether the current request is likely to render the [accordion_metasync] shortcode.
	 *
	 * The shortcode is registered globally, so it can render from more than the
	 * singular post_content: the excerpt, Elementor's `_elementor_data` post meta
	 * (JSON, but has_shortcode() still matches the shortcode text inside it), and
	 * classic Text/Block widgets placed in a sidebar shown on any template.
	 *
	 * This is a static scan, not a guarantee — it can't see every builder/theme
	 * storage format. The `metasync_load_accordion_assets` filter is the escape
	 * hatch: `add_filter('metasync_load_accordion_assets', '__return_true')` to
	 * force-load the accordion assets when a placement here is missed.
	 *
	 * @since    1.0.0
	 * @return   bool
	 */
	private function has_accordion_shortcode()
	{
		$load = false;

		if (is_singular()) {
			$post = get_post();
			if ($post) {
				if (!empty($post->post_content) && has_shortcode($post->post_content, 'accordion_metasync')) {
					$load = true;
				} elseif (!empty($post->post_excerpt) && has_shortcode($post->post_excerpt, 'accordion_metasync')) {
					$load = true;
				} else {
					$elementor_data = get_post_meta($post->ID, '_elementor_data', true);
					if (is_string($elementor_data) && $elementor_data !== '' && has_shortcode($elementor_data, 'accordion_metasync')) {
						$load = true;
					}
				}
			}
		}

		if (!$load) {
			foreach (array('widget_text', 'widget_block') as $widget_option) {
				$instances = get_option($widget_option);
				if (empty($instances) || !is_array($instances)) {
					continue;
				}

				foreach ($instances as $instance) {
					if (!is_array($instance)) {
						continue;
					}
					foreach ($instance as $field_value) {
						if (is_string($field_value) && has_shortcode($field_value, 'accordion_metasync')) {
							$load = true;
							break 3;
						}
					}
				}
			}
		}

		return apply_filters('metasync_load_accordion_assets', $load);
	}

	/**
	 * Add defer attribute to OTTO scripts to prevent render blocking
	 * This improves PageSpeed scores by allowing scripts to load asynchronously
	 *
	 * @since    1.0.0
	 * @param    string    $tag     The script tag
	 * @param    string    $handle  The script handle
	 * @return   string    Modified script tag with defer attribute
	 */
	public function add_defer_attribute($tag, $handle) {
		# This filter is registered globally and also runs on admin screens, where
		# the admin bundle handle ('metasync', js/metasync-admin.js) collides with
		# our own plugin_name handle. Deferring the admin head script would run it
		# after metasync-dashboard.js (its footer-loaded dependent), inverting the
		# dependency order. Bail out on admin requests before the handle check.
		if (is_admin()) {
			return $tag;
		}

		# Only add defer to our OTTO scripts
		if ($handle === $this->plugin_name . '-tracker' || $handle === $this->plugin_name) {
			# Check if a defer attribute is not already present. A plain strpos()
			# for 'defer' false-positives on any script URL that merely contains
			# the substring (e.g. "...?deferred=1"), so match the attribute itself.
			if (!preg_match('/\sdefer(\s|=|>)/i', $tag)) {
				# Add defer attribute before the closing >
				$tag = str_replace(' src', ' defer src', $tag);
			}
		}
		return $tag;
	}

	public function metasync_plugin_init()
	{
		$this->rest_api->rest_authorization_middleware();
		$this->shortcodes_init();
	}

	public function shortcodes_init()
	{
		# add_shortcode('accordion', 'metasync_accordion');

		# name changed from accordion to accordion_metasync to avoid conflict with other plugins
		add_shortcode('accordion_metasync', 'metasync_accordion');
		// add_shortcode('markdown', 'metasync_markdown');


	function metasync_accordion($atts, $content = "")
	{
		// SECURITY FIX: Properly escape shortcode attributes
		$title = isset($atts['title']) ? esc_html($atts['title']) : '';
		$safe_content = wp_kses_post($content);

		$block = "<div class=\"metasync-accordion-block\">
			<button class=\"metasync-accordion\">{$title}</button>
			<div class=\"metasync-panel\">{$safe_content}</div>
			</div>";
		return $block;
	}
	}

	public function metasync_plugin_links($links)
	{
		# Removed Sync link as requested

		# Solving Issue 103
		# get the neneral options into a var
		$general_options = Metasync::get_option('general');

		# set the menu slug to the default
		$menu_slug = 'searchatlas';

		#now check if the menu slug is in the general opts
		if (is_array($general_options) && !empty($general_options['white_label_plugin_menu_slug'])) {

			#set the slug to the modified
			$menu_slug = $general_options['white_label_plugin_menu_slug'];
		}

		$links[] = '<a href="' . get_admin_url(null, 'admin.php?page=' .$menu_slug) . '">' . esc_html__('Settings', 'metasync') . '</a>';
		return $links;
	}

	/**
	 * Enqueue custom CSS stored in post meta for converted pages.
	 * Runs at priority 999 (after theme CSS) so custom styles win.
	 */
	public function enqueue_page_custom_css() {
		if ( ! is_singular() ) {
			return;
		}

		$post_id = get_the_ID();
		if ( ! $post_id ) {
			return;
		}

		$custom_css = get_post_meta( $post_id, '_metasync_custom_css', true );
		if ( empty( $custom_css ) ) {
			return;
		}

		$scoped_css = $this->scope_css_to_body_class( $custom_css, $post_id );

		wp_register_style( 'metasync-page-css-' . $post_id, false );
		wp_enqueue_style( 'metasync-page-css-' . $post_id );
		wp_add_inline_style( 'metasync-page-css-' . $post_id, $scoped_css );
	}

	/**
	 * Enqueue custom CSS into Elementor editor preview iframe.
	 */
	public function enqueue_elementor_editor_css() {
		$post_id = get_the_ID();
		if ( ! $post_id ) {
			return;
		}

		$custom_css = get_post_meta( $post_id, '_metasync_custom_css', true );
		if ( empty( $custom_css ) ) {
			return;
		}

		$scoped_css = $this->scope_css_to_body_class( $custom_css, $post_id );

		wp_register_style( 'metasync-elementor-css-' . $post_id, false );
		wp_enqueue_style( 'metasync-elementor-css-' . $post_id );
		wp_add_inline_style( 'metasync-elementor-css-' . $post_id, $scoped_css );
	}

	/**
	 * Enqueue custom CSS when Divi Visual Builder is active.
	 */
	public function enqueue_divi_builder_css() {
		if ( empty( $_GET['et_fb'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only builder flag for CSS enqueue
			return;
		}

		$post_id = get_the_ID();
		if ( ! $post_id ) {
			return;
		}

		$custom_css = get_post_meta( $post_id, '_metasync_custom_css', true );
		if ( empty( $custom_css ) ) {
			return;
		}

		$scoped_css = $this->scope_css_to_body_class( $custom_css, $post_id );

		wp_register_style( 'metasync-divi-css-' . $post_id, false );
		wp_enqueue_style( 'metasync-divi-css-' . $post_id );
		wp_add_inline_style( 'metasync-divi-css-' . $post_id, $scoped_css );
	}

	/**
	 * Scope all CSS selectors to body.postid-{ID} for higher specificity.
	 *
	 * @param string $css     Raw CSS.
	 * @param int    $post_id Post ID.
	 * @return string Scoped CSS.
	 */
	private function scope_css_to_body_class( $css, $post_id ) {
		$prefix  = 'body.postid-' . intval( $post_id );
		$scoped  = '';
		$pattern = '/([^{]+)\{([^}]*)\}/s';

		preg_match_all( $pattern, $css, $matches, PREG_SET_ORDER );

		foreach ( $matches as $match ) {
			$selectors        = array_map( 'trim', explode( ',', $match[1] ) );
			$scoped_selectors = array_map( function( $sel ) use ( $prefix ) {
				if ( empty( $sel ) || strpos( $sel, '@' ) === 0 ) {
					return $sel;
				}
				return $prefix . ' ' . $sel;
			}, $selectors );
			$scoped .= implode( ', ', $scoped_selectors ) . ' {' . $match[2] . "}\n";
		}

		return $scoped ?: $css;
	}

}

```
