Server IP : 15.235.198.142 / Your IP : 216.73.216.17 Web Server : Apache/2.4.58 (Ubuntu) System : Linux ballsack 6.8.0-45-generic #45-Ubuntu SMP PREEMPT_DYNAMIC Fri Aug 30 12:02:04 UTC 2024 x86_64 User : www-data ( 33) PHP Version : 8.3.6 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/rhodeworks/wp-content/plugins/nextgen-gallery/src/Display/ |
Upload File : |
<?php namespace Imagely\NGG\Display; use Imagely\NGG\DisplayedGallery\Renderer; /** * NGG registers its shortcodes using Shortcodes::add($tag, $callback) * * We do this for the following reason: * * NGG is used with a wide variety of third-party themes and plugins. Some of which, do things * a little unorthodox, and end up modifying the markup generated by the shortcode, which breaks * the display for our users. * * Rather than having to explain to our userbase, "you shouldn't use NGG with [plugin x]", we * decided to find a mechanism that would make our plugin immune from such manipulation from other * plugins, or at least reduce likelihood of such a thing happening. * * This is how the mechanism works: * * When you register a shortcode using Shortcodes::add(), we store a reference to * the callback internally, but add the shortcode will actually call one of the internal methods of * this class which outputs a placeholder (@see $_privateholder_text) instead of the markup generated * by the callback. It's just a simple string and something that a third-party plugin is unlikely to * manipulate in any way. * * We then register a filter for the_content, at high a priority of PHP_INT_MAX as an attempt to make * our hook be the last hook to be executed. This hook then substitutes the placeholders with the markup * generated by the shortcode's callback that was provided in \Imagely\NGG\Display\Shortcodes::add() */ class Shortcodes { private static $_instance = null; private $_shortcodes = []; private $_found = []; private $_placeholder_text = 'ngg_shortcode_%d_placeholder'; /** * @return Shortcodes */ static function get_instance() { if ( \is_null( self::$_instance ) ) { self::$_instance = new Shortcodes(); } return self::$_instance; } /** * Adds a shortcode * * @param $name * @param $callback * @param callable|null Parameters transformer */ static function add( $name, $callback, $transformer = null ) { $manager = self::get_instance(); $manager->add_shortcode( $name, $callback, $transformer ); } /** * @return string[] */ public function get_shortcodes() { return $this->_shortcodes; } public function is_disabled(): bool { return defined( 'NGG_DISABLE_SHORTCODE_MANAGER' ) && NGG_DISABLE_SHORTCODE_MANAGER; } /** * Removes a previously added shortcode * * @param $name */ static function remove( $name ) { $manager = self::get_instance(); $manager->remove_shortcode( $name ); } public function register_hooks() { if ( $this->is_disabled() ) { return; } // For theme & plugin compatibility and to prevent the output of our shortcodes from being // altered we substitute our shortcodes with placeholders at the start of the the_content() filter // queue and then at the end of the the_content() queue, we substitute the placeholders with our // actual markup. \add_filter( 'the_content', [ $this, 'fix_nested_shortcodes' ], -1 ); \add_filter( 'the_content', [ $this, 'parse_content' ], PHP_INT_MAX ); \add_filter( 'widget_text', [ $this, 'fix_nested_shortcodes' ], -1 ); } /** * We're parsing our own shortcodes because WP can't yet handle nested shortcodes [ngg param="[slideshow]"] * * @param string $content * @return string */ public function fix_nested_shortcodes( $content ) { // Try to find each registered shortcode in the content. foreach ( $this->_shortcodes as $tag => $tag_details ) { $shortcode_start_tag = "[{$tag}"; $offset = 0; // Find each occurrence of the shortcode. while ( ( $start_of_shortcode = \strpos( $content, $shortcode_start_tag, $offset ) ) !== false ) { $index = $start_of_shortcode + \strlen( $shortcode_start_tag ); $found_attribute_assignment = false; $current_attribute_enclosure = null; $last_found_char = ''; $content_length = \strlen( $content ) - 1; while ( true ) { // Parse out the shortcode, character-by-character. $char = $content[ $index ]; if ( $char == '"' || $char == "'" && $last_found_char == '=' ) { // Is this the closing quote for an already found attribute assignment? if ( $found_attribute_assignment && $current_attribute_enclosure == $char ) { $found_attribute_assignment = false; $current_attribute_enclosure = null; } else { $found_attribute_assignment = true; $current_attribute_enclosure = $char; } } elseif ( $char == ']' ) { // we've found a shortcode closing tag. But, we need to ensure // that this ] isn't within the value of a shortcode attribute. if ( ! $found_attribute_assignment ) { break; // exit loop - we've found the shortcode. } } $last_found_char = $char; // Prevent infinite loops in our while(TRUE). if ( $index == $content_length ) { break; } ++$index; } // Get the shortcode. $shortcode = \substr( $content, $start_of_shortcode + 1, --$index - $start_of_shortcode ); $shortcode_replacement = \str_replace( [ '[', ']' ], [ '[', ']' ], $shortcode ); // Replace the shortcode with one that doesn't have nested shortcodes. $content = \str_replace( $shortcode, $shortcode_replacement, $content ); // Calculate the offset for the next loop iteration. $offset = $index + 1 + \strlen( $shortcode_replacement ) - \strlen( $shortcode ); } } \reset( $this->_shortcodes ); return $content; } /** * Deactivates all shortcodes */ public function deactivate_all() { foreach ( \array_keys( $this->_shortcodes ) as $shortcode ) { $this->deactivate( $shortcode ); } } /** * Parses the content for shortcodes and returns the substituted content * * @param $content * @return string */ public function parse_content( $content ) { $regex = \str_replace( '%d', '(\d+)', $this->_placeholder_text ); if ( $this->is_rest_request() ) { // Return early if we're in the REST API and shortcodes are disabled. // Allows other plugins to disable shortcodes in the REST API. if ( \apply_filters( 'ngg_disable_shortcodes_in_request_api', true ) ) { return $content; } \ob_start(); } if ( \preg_match_all( "/{$regex}/m", $content, $matches, PREG_SET_ORDER ) ) { foreach ( $matches as $match ) { $placeholder = \array_shift( $match ); $id = \array_shift( $match ); $content = \str_replace( $placeholder, $this->execute_found_shortcode( $id ), $content ); } } if ( $this->is_rest_request() ) { // Pre-generating displayed gallery cache by executing shortcodes in the REST API can prevent users // from being able to add and save blocks with lots of images and no pagination (for example a very large // basic slideshow or pro masonry / mosaic / tile display). if ( \apply_filters( 'ngg_disable_shortcodes_in_request_api', false ) ) { return $content; } \ob_start(); } return $content; } public function render_legacy_shortcode( $params, $inner_content ) { return Renderer::get_instance()->display_images( $params, $inner_content ); } public function execute_found_shortcode( $found_id ) { return isset( $this->_found[ $found_id ] ) ? $this->render_shortcode( $this->_found[ $found_id ]['shortcode'], $this->_found[ $found_id ]['params'], $this->_found[ $found_id ]['inner_content'] ) : 'Invalid shortcode'; } /** * Adds a shortcode * * @param $name * @param $callback * @param callable|null $transformer */ public function add_shortcode( $name, $callback, $transformer = null ) { $this->_shortcodes[ $name ] = [ 'callback' => $callback, 'transformer' => $transformer, ]; if ( $this->is_disabled() ) { // Because render_shortcode() is a wrapper to several shortcodes it requires the $name parameter and can't be passed directly to add_shortcode() add_shortcode( $name, function ( $params, $inner_content ) use ( $name ) { return $this->render_shortcode( $name, $params, $inner_content ); } ); } else { \add_shortcode( $name, [ $this, $name . '____wrapper' ] ); } } /** * Removes a shortcode * * @param $name */ public function remove_shortcode( $name ) { unset( $this->_shortcodes[ $name ] ); $this->deactivate( $name ); } /** * De-activates a shortcode * * @param $shortcode */ public function deactivate( $shortcode ) { if ( isset( $this->_shortcodes[ $shortcode ] ) ) { \remove_shortcode( $shortcode ); } } public function is_rest_request() { return \defined( 'REST_REQUEST' ) || \strpos( $_SERVER['REQUEST_URI'], 'wp-json' ) !== false; } public function __call( $method, $args ) { $params = \array_shift( $args ); $inner_content = \array_shift( $args ); $parts = \explode( '____', $method ); $shortcode = \array_shift( $parts ); if ( \doing_filter( 'the_content' ) && ! \doing_filter( 'widget_text' ) ) { $retval = $this->replace_with_placeholder( $shortcode, $params, $inner_content ); } else { // Widgets don't use placeholders. return $this->render_shortcode( $shortcode, $params, $inner_content ); } return $retval; } public function render_shortcode( $shortcode, $params = [], $inner_content = '' ) { if ( isset( $this->_shortcodes[ $shortcode ] ) ) { $shortcode = $this->_shortcodes[ $shortcode ]; if ( \is_callable( $shortcode['transformer'] ) ) { $params = \call_user_func( $shortcode['transformer'], $params ); } $method = ( \is_null( $shortcode['callback'] ) && \is_callable( $shortcode['transformer'] ) ) ? [ $this, 'render_legacy_shortcode' ] : $shortcode['callback']; $retval = \call_user_func( $method, $params, $inner_content ); } else { $retval = 'Invalid shortcode'; } return $retval; } /** * Prevents wptexturize * * @param $shortcode * @param array $params * @param string $inner_content * @return mixed|void */ public function replace_with_placeholder( $shortcode, $params = [], $inner_content = '' ) { $id = \count( $this->_found ); $this->_found[ $id ] = [ 'shortcode' => $shortcode, 'params' => $params, 'inner_content' => $inner_content, ]; $placeholder = \sprintf( $this->_placeholder_text, $id ); return \apply_filters( 'ngg_shortcode_placeholder', $placeholder, $shortcode, $params, $inner_content ); } /** * @return string */ public function get_shortcode_regex() { $keys = \array_keys( $this->_shortcodes ); return '/' . \get_shortcode_regex( $keys ) . '/'; } }