HEX
Server: LiteSpeed
System: Linux cde2.duelhost.dk 4.18.0-553.34.1.lve.el8.x86_64 #1 SMP Thu Jan 9 16:30:32 UTC 2025 x86_64
User: dtptviut (1121)
PHP: 8.0.30
Disabled: exec,system,passthru,shell_exec,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
Upload Files
File: /home/dtptviut/public_html/wp-content/plugins/nitropack/classes/Feature/AjaxShortcodes.php
<?php

namespace NitroPack\Feature;

class AjaxShortcodes {
	const STAGE = "very_early";

	private $shortcodes = array();
	private $scriptHandle = 'nitropack-ajax-shortcodes';
	private $processedShortcodes = [];
	private $emptyScript = "let nitroAjaxShortcode = false";

	/**
	 * Get the current post id
	 * @return int
	 */
	private function getCurrentPostId() {
		$post_id = get_the_ID();

		if ( ! $post_id ) {
			$post_id = get_queried_object_id();
		}

		return $post_id ? (int) $post_id : 0;
	}

	private function generateNitroScLoader() {
		if ( empty( $this->processedShortcodes ) )
			return $this->emptyScript;

		$adminUrl = admin_url( 'admin-ajax.php' );
		$js = <<<SCRIPT
    (async () => {
        let domIsReady = new Promise((resolve) => {
            if (document.readyState !== 'complete') {
                document.addEventListener('DOMContentLoaded', resolve);
            } else {
                resolve();
            }
        });

        let formData = new URLSearchParams();
        formData.append("action", "nitro_shortcode_ajax");  
        formData.append("nonce", "NONCE_VALUE");
        formData.append("tags", "TAGS_JSON_DATA");

        fetch('$adminUrl', {
            method: "POST",
            headers: {
                "Content-Type": "application/x-www-form-urlencoded",
            },
            body: formData.toString(),
        })
        .then(response => response.json())
        .then(async (data) => {
            let scData = data.data;
            await domIsReady;
            for (let key in scData) {
                document.querySelectorAll(`.nitro-sc-load[data-sc-meta-id='\${key}']`).forEach(el => {
                    el.outerHTML = scData[key];
                });            
            }
        })
        .catch(error => {
            console.error('Error:', error);
        });
    })()

SCRIPT;

		return str_replace(
			[
				"NONCE_VALUE",
				"TAGS_JSON_DATA"
			],
			[
				$this->generateNonce( $this->processedShortcodes ),
				base64_encode( json_encode( $this->processedShortcodes ) )
			],
			$js
		);
	}

	private function generateNonce( $tags ) {
		return wp_create_nonce( $this->getNonceAction( $tags ) );
	}

	private function getNonceAction( $tags ) {
		$jsons = array_map( "json_encode", $tags );
		sort( $jsons );
		return base64_encode( json_encode( $jsons ) );
	}
	/* Merge shortcodes from wp-config and site config */
	private function mergeShortcodes() {

		$siteConfig = get_nitropack()->getSiteConfig();
		$options = $siteConfig['options_cache'];

		$ajax_shortcodes = isset( $options['ajaxShortcodes'] ) ? $options['ajaxShortcodes'] : null; //initially ajaxShortcodes is empty

		if ( self::isEnabled() && ( ! empty( $ajax_shortcodes['shortcodes'] ) && is_array( $ajax_shortcodes['shortcodes'] ) ) ) {
			$this->shortcodes = $ajax_shortcodes['shortcodes'];
		}
		if ( defined( "NITROPACK_AJAX_SHORTCODES" ) && strpos( NITROPACK_AJAX_SHORTCODES, ',' ) !== false ) {
			$wp_config_shortcodes = explode( ',', NITROPACK_AJAX_SHORTCODES );
			$this->shortcodes = array_unique( array_merge( $this->shortcodes, $wp_config_shortcodes ) );
		}
	}

	/**
	 * Add post_id as context, so simple shortcodes like get_the_title and get_the_date work
	 * @param mixed $stage
	 * @return bool
	 */
	public function init( $stage ) {
		if ( ! defined( "NITROPACK_AJAX_SHORTCODES" ) && ! self::isEnabled() ) {
			// This init method can be run at any stage. This gives the opportunity to define the constant at a later point
			// For example in a MU plugin
			return true;
		}
		$this->mergeShortcodes();
		//stop execution if no shortcodes are found
		if ( ! $this->shortcodes )
			return;

		add_filter( 'pre_do_shortcode_tag', function ( $out, $tag, $attr ) {

			if ( defined( "NITRO_DOING_AJAX_SHORTCODES" ) && NITRO_DOING_AJAX_SHORTCODES )
				return $out;

			if ( in_array( $tag, $this->shortcodes ) ) {
				$entry = [
					"tag" => $tag,
					"attr" => $attr,
					"post_id" => $this->getCurrentPostId(),
				];
				if ( ! in_array( $entry, $this->processedShortcodes, true ) ) {
					$this->processedShortcodes[] = $entry;
				}
				$np_out = '<span class="nitro-sc-load" data-sc-meta-id="' . base64_encode( json_encode( $entry ) ) . '"></span>';

				return $np_out;
			}

			return $out;
		}, 10, 3 );


		add_action( 'wp_ajax_nitro_shortcode_ajax', array( $this, 'shortcodeAjax' ) );
		add_action( 'wp_ajax_nopriv_nitro_shortcode_ajax', array( $this, 'shortcodeAjax' ) );

		add_action( 'wp_enqueue_scripts', [ $this, 'add_scripts' ] );
		$this->startBuffering();
	}

	private function startBuffering() {
		ob_start( function ( $buffer ) {
			return str_replace( $this->emptyScript, $this->generateNitroScLoader(), $buffer );
		}, 0, PHP_OUTPUT_HANDLER_FLUSHABLE | PHP_OUTPUT_HANDLER_REMOVABLE );
	}

	public function add_scripts() {
		wp_register_script( $this->scriptHandle, '', [], '1.0.0', false );
		wp_add_inline_script( $this->scriptHandle, $this->emptyScript );
		wp_enqueue_script( $this->scriptHandle );

		add_filter( 'wp_inline_script_attributes', function ( $attr, $js ) {

			if ( isset( $attr['id'] ) && strpos( $attr['id'], $this->scriptHandle ) === 0 ) {
				$attr = array_merge( array( 'nitro-exclude' => true ), $attr );
			}

			return $attr;

		}, 10, 2 );
	}

	public function shortcodeAjax() {
		if ( ! defined( "NITRO_DOING_AJAX_SHORTCODES" ) )
			define( "NITRO_DOING_AJAX_SHORTCODES", true );
		$this->runShortcodes();

		// In case a later hook is needed, we can and an option for it and use something like this
		//if (did_action('plugins_loaded')) {
		//    $this->runShortcodes();
		//} else {
		//    add_action('plugins_loaded', [$this, 'runShortcodes']);
		//}
	}

	private function validateNonce( $tags ) {
		if ( empty( $_POST['nonce'] ) || empty( $_POST['tags'] ) || ! is_string( $_POST['tags'] ) ) {
			die( __( 'Security check', 'nitropack' ) );
		}

		$action = $this->getNonceAction( $tags );
		if ( ! wp_verify_nonce( $_POST['nonce'], $action ) )
			die( __( 'Security check', 'nitropack' ) );
	}
	/**
	 * The AJAX call for the shortcodes is made in the context of the current post, so we need to set up the post data for each shortcode that has a post_id
	 * This method will run the shortcodes and return the output as a JSON response
	 * @return void
	 */
	public function runShortcodes() {
		$tags = ! empty( $_POST["tags"] ) && is_string( $_POST["tags"] ) ? json_decode( base64_decode( $_POST['tags'] ), true ) : [];
		if ( empty( $tags ) )
			wp_send_json_error( [ "message" => "Invalid shortcode input" ], 400 );
		$this->validateNonce( $tags );

		$resp = [];
		$previous_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;

		foreach ( $tags as $sc ) {
			if ( ! empty( $sc['post_id'] ) ) {
				$post = get_post( (int) $sc['post_id'] );

				if ( $post ) {
					$GLOBALS['post'] = $post;
					setup_postdata( $post );
				}
			}

			$attrFlat = is_array( $sc["attr"] ) ? array_map( function ( $k, $v ) {
				return "$k=\"$v\"";
			}, array_keys( $sc["attr"] ), array_values( $sc["attr"] ) ) : [];
			$arrKey = base64_encode( json_encode( $sc ) );
			$resp[ $arrKey ] = do_shortcode( "[{$sc['tag']} " . implode( " ", $attrFlat ) . "]" );
		}

		if ( $previous_post ) {
			$GLOBALS['post'] = $previous_post;
			setup_postdata( $previous_post );
		} else {
			unset( $GLOBALS['post'] );
			wp_reset_postdata();
		}

		wp_send_json_success( $resp );
	}
	public function isEnabled() {
		$siteConfig = get_nitropack()->getSiteConfig();
		return ! empty( $siteConfig['options_cache']['ajaxShortcodes']['enabled'] )
			&& $siteConfig['options_cache']['ajaxShortcodes']['enabled'] == 1;
	}
}