\s*$|', '', $snippet->code ); // Deactivate snippet if code contains errors. if ( $snippet->active && 'single-use' !== $snippet->scope ) { test_snippet_code( $snippet ); if ( $snippet->code_error ) { $snippet->active = 0; } } } // Increment the revision number unless revision = 1 or revision is not set. if ( $snippet->revision && $snippet->revision > 1 ) { $snippet->increment_revision(); } // Shared network snippets are always considered inactive. $snippet->active = $snippet->active && ! $snippet->shared_network; // Build the list of data to insert (excluding locked, which is stored in wp_options). $data = [ 'name' => $snippet->name, 'description' => $snippet->desc, 'code' => $snippet->code, 'tags' => $snippet->tags_list, 'scope' => $snippet->scope, 'condition_id' => intval( $snippet->condition_id ), 'priority' => $snippet->priority, 'active' => intval( $snippet->active ), 'modified' => $snippet->modified, 'revision' => $snippet->revision, 'cloud_id' => $snippet->cloud_id_owner ? $snippet->cloud_id_owner : null, ]; // Create a new snippet if the ID is not set. if ( 0 === $snippet->id ) { $result = $wpdb->insert( $table, $data, '%s' ); if ( false === $result ) { return null; } $snippet->id = $wpdb->insert_id; $updated = get_snippet( $snippet->id, $snippet->network ); $updated->code_error = $snippet->code_error; $updated->code_error_trace = $snippet->code_error_trace; do_action( 'code_snippets/create_snippet', $updated, $table ); if ( $updated->id > 0 ) { set_snippet_locked( $updated->id, $updated->locked, $updated->network ); } } else { // Otherwise, update the snippet data. $existing = get_snippet( $snippet->id, $snippet->network ); set_snippet_locked( $snippet->id, $snippet->locked, $snippet->network ); $wpdb->update( $table, $data, [ 'id' => $snippet->id ], null, [ '%d' ] ); $updated = get_snippet( $snippet->id, $snippet->network ); $updated->code_error = $snippet->code_error; $updated->code_error_trace = $snippet->code_error_trace; do_action( 'code_snippets/update_snippet', $updated, $table, $existing, $snippet ); if ( ! $updated->active && $existing->active ) { $recently_active = get_self_option( $updated->network, 'recently_active_snippets', [] ); $recently_active[ $updated->id ] = time(); update_self_option( $updated->network, 'recently_active_snippets', $recently_active ); } elseif ( ! $updated->active ) { $recently_active = get_self_option( $updated->network, 'recently_active_snippets', [] ); if ( isset( $recently_active[ $updated->id ] ) ) { unset( $recently_active[ $updated->id ] ); update_self_option( $updated->network, 'recently_active_snippets', $recently_active ); } } } update_shared_network_snippets( [ $updated ] ); clean_snippets_cache( $table ); return $updated; } /** * Execute a snippet. * Execute operation. * * Code must NOT be escaped, as it will be executed directly. * * @param string $code Snippet code to execute. * @param int $id Snippet ID. * @param bool $force Force snippet execution, even if save mode is active. * * @return Throwable|mixed Code error if encountered during execution, or result of snippet execution otherwise. * * @since 2.0.0 * @noinspection PhpUndefinedConstantInspection * * phpcs:disable Squiz.PHP.Eval.Discouraged */ function execute_snippet( string $code, int $id = 0, bool $force = false ) { /** * Do not continue if safe mode is active. * * @noinspection PhpUndefinedConstantInspection */ if ( empty( $code ) || ( ! $force && defined( 'CODE_SNIPPETS_SAFE_MODE' ) && CODE_SNIPPETS_SAFE_MODE ) ) { return false; } ob_start(); try { $result = eval( $code ); } catch ( Throwable $throwable ) { $result = $throwable; } ob_end_clean(); do_action( 'code_snippets/after_execute_snippet', $code, $id, $result ); return $result; } /** * Retrieve a single snippets from the database using its cloud ID. * * Read operation. * * @param string $cloud_id The Cloud ID of the snippet to retrieve. * @param bool|null $multisite Retrieve a multisite-wide snippet (true) or site-wide snippet (false). * * @return Snippet|null A single snippet object or null if no snippet was found. * * @since 3.5.0 */ function get_snippet_by_cloud_id( string $cloud_id, ?bool $multisite = null ): ?Snippet { global $wpdb; $multisite = DB::validate_network_param( $multisite ); $table_name = code_snippets()->db->get_table_name( $multisite ); $cached_snippets = wp_cache_get( "all_snippets_$table_name", CACHE_GROUP ); // Attempt to fetch snippet from the cached list, if it exists. if ( is_array( $cached_snippets ) ) { foreach ( $cached_snippets as $snippet ) { if ( $snippet->cloud_id === $cloud_id ) { return apply_filters( 'code_snippets/get_snippet_by_cloud_id', $snippet, $cloud_id, $multisite ); } } } // Otherwise, search for the snippet from the database. $snippet_data = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE cloud_id = %s", $cloud_id ) ); // cache pass, db call ok. $snippet = $snippet_data ? new Snippet( $snippet_data ) : null; // Load locked from wp_options if snippet exists. if ( $snippet && $snippet->id > 0 ) { $snippet->network = $multisite; $snippet->locked = is_snippet_locked( $snippet->id, $multisite ); } return apply_filters( 'code_snippets/get_snippet_by_cloud_id', $snippet, $cloud_id, $multisite ); } /** * Update a snippet entry given a list of fields. * Write operation. * * @param int $snippet_id ID of the snippet to update. * @param array $fields An array of fields mapped to their values. * @param bool|null $network Update in network-wide (true) or site-wide (false) table. */ function update_snippet_fields( int $snippet_id, array $fields, ?bool $network = null ) { global $wpdb; $network = DB::validate_network_param( $network ); $table = code_snippets()->db->get_table_name( $network ); // Build a new snippet object for the validation. $snippet = new Snippet(); $snippet->id = $snippet_id; // Validate fields through the snippet class and copy them into a clean array. $clean_fields = array(); $locked_value = null; foreach ( $fields as $field => $value ) { // Handle locked separately (stored in wp_options). if ( 'locked' === $field ) { if ( $snippet->set_field( $field, $value ) ) { $locked_value = $snippet->$field; } continue; } if ( $snippet->set_field( $field, $value ) ) { $clean_fields[ $field ] = $snippet->$field; } } // Update the snippet in the database (excluding locked). if ( ! empty( $clean_fields ) ) { $wpdb->update( $table, $clean_fields, array( 'id' => $snippet->id ), null, array( '%d' ) ); } // Save locked to wp_options if it was provided. if ( null !== $locked_value ) { set_snippet_locked( $snippet->id, $locked_value, $network ); } clean_snippets_cache( $table ); $updated = get_snippet( $snippet->id, $network ); if ( $updated->id ) { do_action( 'code_snippets/update_snippet', $updated, $table ); } } /** * Evaluate a snippet by loading it from the filesystem. * * @param string $code Snippet code. * @param string $file Snippet filename. * @param int $id Snippet ID. * @param bool $force Force snippet execution, even if save mode is active. * * @return bool|Exception|Throwable|null Code error if encountered during execution, or result of snippet execution otherwise. */ function execute_snippet_from_flat_file( string $code, string $file, int $id = 0, bool $force = false ) { if ( ! is_file( $file ) ) { execute_snippet( $code, $id, $force ); return true; } /* @noinspection PhpUndefinedConstantInspection */ if ( ! $force && defined( 'CODE_SNIPPETS_SAFE_MODE' ) && CODE_SNIPPETS_SAFE_MODE ) { return false; } ob_start(); try { require_once $file; $result = null; } catch ( Throwable $throwable ) { $result = $throwable; } ob_end_clean(); do_action( 'code_snippets/after_execute_snippet_from_flat_file', $file, $id ); return $result ?? null; } \n\n" . $snippet->code ); return ob_get_clean(); } /** * Evaluate a snippet by loading its code from the filesystem. * * @param string $filepath Path to file to evaluate. * @param array $atts Shortcode attributes. * * @return string Snippet output. */ private function evaluate_shortcode_from_flat_file( string $filepath, array $atts ): string { ob_start(); ( function ( $atts ) use ( $filepath ) { /** * Avoiding extract is typically recommended, however in this situation we want to make it easy for snippet * authors to use custom attributes. * * phpcs:ignore WordPress.PHP.DontExtract.extract_extract */ extract( $atts, EXTR_SKIP ); require_once $filepath; } )( $atts ); return ob_get_clean(); } /** * Retrieve a content snippet from the filesystem or the database. * * @param int $id Snippet identifier. * @param bool $network Whether the snippet is network-wide. * * @return Snippet|null */ private function get_content_snippet( int $id, bool $network ): ?Snippet { if ( ! Snippet_Files::is_active() ) { return get_snippet( $id, $network ); } $validated_network = DB::validate_network_param( $network ); $table_name = Snippet_Files::get_hashed_table_name( code_snippets()->db->get_table_name( $validated_network ) ); $handler = code_snippets()->snippet_handler_registry->get_handler( 'html' ); $config_filepath = Snippet_Files::get_base_dir( $table_name, $handler->get_dir_name() ) . '/index.php'; if ( file_exists( $config_filepath ) ) { $config = require_once $config_filepath; $snippet_data = $config[ $id ] ?? null; if ( $snippet_data ) { $snippet = new Snippet( $snippet_data ); return apply_filters( 'code_snippets/get_snippet', $snippet, $id, $network ); } } return get_snippet( $id, $network ); } /** * Render the value of a content shortcode * * @param array $atts Shortcode attributes. * * @return string Shortcode content. */ public function render_content_shortcode( array $atts ): string { $atts = $this->convert_boolean_attribute_flags( $atts, [ 'network', 'php', 'format', 'shortcodes', 'debug' ] ); $original_atts = $atts; $atts = shortcode_atts( [ 'id' => 0, 'snippet_id' => 0, 'network' => false, 'php' => false, 'format' => false, 'shortcodes' => false, 'debug' => false, ], $atts, self::CONTENT_SHORTCODE ); $id = 0 !== intval( $atts['snippet_id'] ) ? intval( $atts['snippet_id'] ) : intval( $atts['id'] ); if ( ! $id ) { return $this->invalid_id_warning( $id ); } $snippet = $this->get_content_snippet( $id, (bool) $atts['network'] ); // Render the source code if this is not a shortcode snippet. if ( 'content' !== $snippet->scope ) { return $snippet->id ? $this->render_snippet_source( $snippet ) : $this->invalid_id_warning( $snippet->id ); } // If the snippet is inactive, either display a message or render nothing. if ( ! $snippet->active ) { if ( ! $atts['debug'] ) { return ''; } /* translators: 1: snippet name, 2: snippet edit link */ $text = __( '%1$s is currently inactive. You can edit this snippet to activate it and make it visible. This message will not appear in the published post.', 'code-snippets' ); $snippet_name = '' . $snippet->name . ''; $edit_url = add_query_arg( 'id', $snippet->id, code_snippets()->get_menu_url( 'edit' ) ); return wp_kses( sprintf( $text, $snippet_name, $edit_url ), [ 'strong' => [], 'a' => [ 'href' => [], ], ] ); } $content = $this->evaluate_shortcode_content( $snippet, $original_atts ); if ( $atts['format'] ) { $functions = [ 'wptexturize', 'convert_smilies', 'convert_chars', 'wpautop', 'capital_P_dangit' ]; foreach ( $functions as $function ) { $content = call_user_func( $function, $content ); } } if ( $atts['shortcodes'] ) { // Temporarily remove this shortcode from the list to prevent recursion while executing do_shortcode. remove_shortcode( self::CONTENT_SHORTCODE ); $content = do_shortcode( $atts['format'] ? shortcode_unautop( $content ) : $content ); add_shortcode( self::CONTENT_SHORTCODE, [ $this, 'render_content_shortcode' ] ); } return apply_filters( 'code_snippets/content_shortcode', $content, $snippet, $atts, $original_atts ); } /** * Converts a value and key into an HTML attribute pair. * * @param string $value Attribute value. * @param string $key Attribute name. * * @return void */ private static function create_attribute_pair( string &$value, string $key ) { $value = sprintf( '%s="%s"', sanitize_key( $key ), esc_attr( $value ) ); } /** * Render the source code of a given snippet * * @param Snippet $snippet Snippet object. * @param array $atts Shortcode attributes. * * @return string Shortcode content. */ private function render_snippet_source( Snippet $snippet, array $atts = [] ): string { $atts = array_merge( array( 'line_numbers' => false, 'highlight_lines' => '', ), $atts ); $language = 'css' === $snippet->type ? 'css' : ( 'js' === $snippet->type ? 'js' : 'php' ); $pre_attributes = array( 'id' => "code-snippet-source-$snippet->id", 'class' => 'code-snippet-source', ); $code_attributes = array( 'class' => "language-$language", ); if ( $atts['line_numbers'] ) { $code_attributes['class'] .= ' line-numbers'; $pre_attributes['class'] .= ' linkable-line-numbers'; } if ( $atts['highlight_lines'] ) { $pre_attributes['data-line'] = $atts['highlight_lines']; } $pre_attributes = apply_filters( 'code_snippets/prism_pre_attributes', $pre_attributes, $snippet, $atts ); $code_attributes = apply_filters( 'code_snippets/prism_code_attributes', $code_attributes, $snippet, $atts ); array_walk( $code_attributes, array( $this, 'create_attribute_pair' ) ); array_walk( $pre_attributes, array( $this, 'create_attribute_pair' ) ); $code = 'php' === $snippet->type ? "code" : $snippet->code; $output = sprintf( '
%s
', implode( ' ', $pre_attributes ), implode( ' ', $code_attributes ), esc_html( $code ) ); return apply_filters( 'code_snippets/render_source_shortcode', $output, $snippet, $atts ); } /** * Render the value of a source shortcode * * @param array $atts Shortcode attributes. * * @return string Shortcode content. */ public function render_source_shortcode( array $atts ): string { $atts = $this->convert_boolean_attribute_flags( $atts, [ 'network', 'line_numbers' ] ); $atts = shortcode_atts( array( 'id' => 0, 'snippet_id' => 0, 'network' => false, 'line_numbers' => false, 'highlight_lines' => '', ), $atts, self::SOURCE_SHORTCODE ); $id = 0 !== intval( $atts['snippet_id'] ) ? intval( $atts['snippet_id'] ) : intval( $atts['id'] ); if ( ! $id ) { return $this->invalid_id_warning( $id ); } $snippet = $this->get_content_snippet( $id, (bool) $atts['network'] ); return $this->render_snippet_source( $snippet, $atts ); } }