diff --git a/assets/js/insertcodes-admin.asset.php b/assets/js/insertcodes-admin.asset.php new file mode 100644 index 0000000..6cf6b1a --- /dev/null +++ b/assets/js/insertcodes-admin.asset.php @@ -0,0 +1 @@ + array(), 'version' => '0e0f7bc8e55c70c67db9'); diff --git a/assets/js/insertcodes-admin.js b/assets/js/insertcodes-admin.js index 1922d4c..adabb55 100644 --- a/assets/js/insertcodes-admin.js +++ b/assets/js/insertcodes-admin.js @@ -1,19 +1 @@ -(function ($) { - 'use strict'; - $(window).on('load', function () { - $.ready.then(function () { - var defaultSettings = wp.codeEditor.defaultSettings ? _.clone(wp.codeEditor.defaultSettings) : {}; - - // HTML Editor. - var htmlSettings = _.extend({}, defaultSettings, { - codemirror: _.extend({}, defaultSettings.codemirror, { - mode: 'htmlmixed' - }) - }); - - wp.codeEditor.initialize($('#insertcodes_header'), htmlSettings); - wp.codeEditor.initialize($('#insertcodes_body'), htmlSettings); - wp.codeEditor.initialize($('#insertcodes_footer'), htmlSettings); - }); - }); -})(jQuery); +!function(e){"use strict";e(window).on("load",(function(){e.ready.then((function(){var o=wp.codeEditor.defaultSettings?_.clone(wp.codeEditor.defaultSettings):{},i=_.extend({},o,{codemirror:_.extend({},o.codemirror,{mode:"htmlmixed"})});wp.codeEditor.initialize(e("#insertcodes_header"),i),wp.codeEditor.initialize(e("#insertcodes_body"),i),wp.codeEditor.initialize(e("#insertcodes_footer"),i)})),e.ready.then((function(){var o=wp.codeEditor.defaultSettings?_.clone(wp.codeEditor.defaultSettings):{},i=_.extend({},o,{codemirror:_.extend({},o.codemirror,{mode:"application/x-httpd-php"})}),t=wp.codeEditor.initialize(e("#insertcodes_php"),i),d=t.codemirror.getValue();0!==d.indexOf(" 'text/html' ) ); + // Conditionally enqueue the code editor and admin script. + if ( in_array( $hook, array( 'toplevel_page_insert-codes', 'insert-codes_page_insert-codes-snippets' ), true ) ) { + $settings = wp_enqueue_code_editor( array( 'type' => 'application/x-httpd-php' ) ); // Return if the editor was not enqueued. if ( false === $settings ) { @@ -113,6 +144,7 @@ public function enqueue_scripts( $hook ) { wp_enqueue_script( 'insertcodes-admin' ); } + // Enqueue the admin style for the defined screens. if ( in_array( $hook, $screens, true ) ) { wp_enqueue_style( 'insertcodes-admin' ); } diff --git a/includes/Admin/views/snippets.php b/includes/Admin/views/snippets.php new file mode 100644 index 0000000..d31a36b --- /dev/null +++ b/includes/Admin/views/snippets.php @@ -0,0 +1,99 @@ + +
+
+
+

+ +

+

+
+
+
+
+
+
+

+
+
+
+ +
+
+ +

+
+
+
+

+
+
+
+ +
+
+ +

+
+
+
+
+ +
+
+ +

+
+
+
+ + +
+ +
+
+
+
+
+
+
+

+
+
+
    +
  • +

    + + +

    +
  • +
  • +

    + + +

    +
  • +
+
+
+
+
+
+
+add_flash_notice( __( 'You do not have sufficient permissions to perform this action.', 'insert-codes' ) ); + } + $header_scripts = isset( $_POST['insertcodes_header'] ) ? wp_kses( wp_unslash( $_POST['insertcodes_header'] ), insertcodes_get_allowed_html() ) : ''; $body_scripts = isset( $_POST['insertcodes_body'] ) ? wp_kses( wp_unslash( $_POST['insertcodes_body'] ), insertcodes_get_allowed_html() ) : ''; $footer_scripts = isset( $_POST['insertcodes_footer'] ) ? wp_kses( wp_unslash( $_POST['insertcodes_footer'] ), insertcodes_get_allowed_html() ) : ''; @@ -43,6 +49,42 @@ public static function handle_hbf_scripts() { exit(); } + /** + * Updating PHP code snippets. + * + * @since 1.2.0 + * @return void + */ + public static function handle_snippets() { + check_admin_referer( 'insertcodes_snippets' ); + + // User capability check. You must have manage_options capability to perform this action. + if ( ! current_user_can( 'manage_options' ) ) { + insertcodes()->add_flash_notice( __( 'You do not have sufficient permissions to perform this action.', 'insert-codes' ) ); + } + + // Get the sanitized PHP code snippets. + $php_snippets = self::sanitize_snippet( $_POST ); + + // Get settings value. + $enable_snippets = isset( $_POST['insertcodes_enable_snippets'] ) ? sanitize_key( wp_unslash( $_POST['insertcodes_enable_snippets'] ) ) : ''; + $location = isset( $_POST['insertcodes_snippets_location'] ) ? sanitize_key( wp_unslash( $_POST['insertcodes_snippets_location'] ) ) : ''; + + // If 'add_flash_notice( __( 'PHP code snippets saved successfully.', 'insert-codes' ) ); + wp_safe_redirect( wp_get_referer() ); + exit(); + } + /** * Updating settings. * @@ -52,6 +94,11 @@ public static function handle_hbf_scripts() { public static function handle_settings() { check_admin_referer( 'insertcodes_settings' ); + // User capability check. You must have manage_options capability to perform this action. + if ( ! current_user_can( 'manage_options' ) ) { + insertcodes()->add_flash_notice( __( 'You do not have sufficient permissions to perform this action.', 'insert-codes' ) ); + } + $headers_priority = isset( $_POST['insertcodes_header_priority'] ) ? intval( wp_unslash( $_POST['insertcodes_header_priority'] ) ) : intval( '10' ); $body_priority = isset( $_POST['insertcodes_body_priority'] ) ? intval( wp_unslash( $_POST['insertcodes_body_priority'] ) ) : intval( '10' ); $footers_priority = isset( $_POST['insertcodes_footer_priority'] ) ? intval( wp_unslash( $_POST['insertcodes_footer_priority'] ) ) : intval( '10' ); @@ -67,4 +114,18 @@ public static function handle_settings() { wp_safe_redirect( wp_get_referer() ); exit(); } + + /** + * Sanitize PHP codes. + * + * @param array $data POST data. + * + * @since 1.2.0 + * @return string $codes Sanitized PHP codes. + */ + public static function sanitize_snippet( $data ) { + $codes = isset( $data['insertcodes_php'] ) ? wp_unslash( $data['insertcodes_php'] ) : ''; + + return $codes; + } } diff --git a/includes/Controllers/ExecutableCodes.php b/includes/Controllers/ExecutableCodes.php new file mode 100644 index 0000000..37031cb --- /dev/null +++ b/includes/Controllers/ExecutableCodes.php @@ -0,0 +1,72 @@ + 'php', + 'location' => get_option( 'insertcodes_snippets_location', 'everywhere' ), + 'priority' => 'default', + 'hook' => 'init', + ); + + // Get the executable code snippets. + $code_snippets = get_option( 'insertcodes_php', '' ); + + // Check if the code snippets is empty. + if ( empty( $code_snippets ) ) { + return; + } + + // phpcs:disable + // TODO: We should Implode all the code and execute it when we supported multiple Code Snippets. + // Example: $code_snippets = implode( PHP_EOL, $code_snippets );. + // Loop through the code snippets. + /* + foreach ( $code_snippets as $code_snippet ) { + // Execute the code snippet. + $this->execute( + $code_snippet['code'], + $code_snippet['type'], + $code_snippet['location'], + $code_snippet['priority'], + $code_snippet['hook'], + $code_snippet['args'] + ); + } + */ + // phpcs:enable + + // Execute the code snippets. + new Execute( $code_snippets, $args ); + } +} diff --git a/includes/Controllers/Execute.php b/includes/Controllers/Execute.php new file mode 100644 index 0000000..44d638a --- /dev/null +++ b/includes/Controllers/Execute.php @@ -0,0 +1,248 @@ +error = null; + $this->execute( $code, $args ); + } + + /** + * Validate the php code snippet. + * + * @param string $code The code snippet. + * + * @since 1.2.0 + * @return bool + */ + public function validate_code( $code ) { + $tokens = @token_get_all( 'error = 'Unexpected closing brace'; + return false; + } + } + } else { + list( $id, $text ) = $token; + if ( T_INLINE_HTML === $id ) { + $this->error = 'Unexpected inline HTML'; + return false; + } elseif ( T_CONSTANT_ENCAPSED_STRING === $id || T_ENCAPSED_AND_WHITESPACE === $id ) { + $in_string = ! $in_string; + } elseif ( T_COMMENT === $id || T_DOC_COMMENT === $id ) { + // Ignore comments. + continue; + } + + if ( T_STRING === $id && ( 'die(' === $text || 'wp_die' === $text ) ) { + $this->error = 'Usage of die() or wp_die() is not allowed'; + return false; + } + // phpcs:disable + // TODO: We should add more checks to validate the code snippet like: + // elseif ( T_VARIABLE === $id && ! $in_string && ! defined( $text ) ) { + // $this->error = 'Invalid variable usage'; + // return false; + // } + // phpcs:enable + } + } + + if ( 0 !== $braces ) { + $this->error = 'Unmatched braces'; + return false; + } + + return true; + } + + /** + * Execute the code snippet. + * + * @param string $code The code snippet. + * @param array $args The arguments to pass to the code snippet. + * + * @since 1.2.0 + * @return void|string + */ + public function execute( $code, $args = array() ) { + // Check if the code is empty. + if ( empty( $code ) ) { + return; + } + + // Default arguments. + $defaults = array( + 'type' => 'php', + 'location' => 'everywhere', + 'priority' => 'default', + 'hook' => 'init', + ); + // Parse the arguments. + $args = wp_parse_args( $args, $defaults ); + + // Check if the code type is set. + if ( ! isset( $args['type'] ) ) { + return; + } + + // Check if the location is set. + if ( ! isset( $args['location'] ) ) { + return; + } + + // Check if the priority is set. + if ( ! isset( $args['priority'] ) ) { + return; + } + + // Check if the hook is set. + if ( ! isset( $args['hook'] ) ) { + return; + } + + // Check if the hook is valid. + if ( ! has_action( $args['hook'] ) ) { + return; + } + + // Check if the code type is valid. + if ( ! in_array( $args['type'], array( 'php', 'html', 'css', 'js' ), true ) ) { + return; + } + + // Check if the location is valid. + if ( ! in_array( $args['location'], array( 'everywhere', 'frontend_only', 'admin_only' ), true ) ) { + return; + } + + // Check if the priority is valid. + if ( ! in_array( $args['priority'], array( 'high', 'default', 'low' ), true ) ) { + return; + } + + // Check if the code snippet is valid. + if ( ! $this->validate_code( $code ) ) { + $this->maybe_disable_snippet( $this->error ); + return; + } + + // Don't allow executing suspicious code. + if ( self::is_code_not_allowed( $code ) ) { + $this->maybe_disable_snippet( __( 'Suspicious code detected. Maybe the code snippet contains a disallowed function: wp_die, die, exit, eval.', 'insert-codes' ) ); + return; + } + + // Check the location value and return if not match. + if ( 'frontend_only' === $args['location'] && is_admin() ) { + return; + } + + if ( 'admin_only' === $args['location'] && ! is_admin() ) { + return; + } + + $error = false; + + // Execute the code snippet based on the code type. + switch ( $args['type'] ) { + case 'php': + // Execute the PHP code snippet. + try { + eval( $code ); // phpcs:ignore Squiz.PHP.Eval.Discouraged + } catch ( \Error $e ) { + $error = array( + 'message' => $e->getMessage(), + 'line' => $e->getLine(), + ); + } + if ( $error ) { + $this->maybe_disable_snippet( $error['message'], $error['line'] ); + } + break; + } + } + + /** + * Add a method to detect suspicious code. + * + * @param string $code The code to check. + * + * @since 1.2.0 + * @return bool + */ + public static function is_code_not_allowed( $code ) { + if ( preg_match_all( '/(base64_decode|error_reporting|ini_set|eval)\s*\(/i', $code, $matches ) ) { + if ( count( $matches[0] ) > 5 ) { + return true; + } + } + + if ( preg_match( '/dns_get_record/i', $code ) ) { + return true; + } + + // if 'wp_die', 'die', 'exit' or 'eval' is present in the code, then remove it. + if ( preg_match( '/(@?\\\\?(die|wp_die|exit)\s*\(?)/i', $code ) ) { + return true; + } + + return false; + } + + /** + * Maybe disable the snippet. + * + * @param string $error The error message. + * @param string $line The line number. + * + * @since 1.2.0 + * @return void + */ + public function maybe_disable_snippet( $error, $line = null ) { + update_option( 'insertcodes_enable_snippets', 'no' ); + + // Add a flash notice. + if ( $line ) { + $error = sprintf( '%s on line %d', $error, ( absint( $line ) - 1 ) ); + } + + insertcodes()->add_flash_notice( sprintf( '%s And the PHP code snippets has been disabled.', $error ), 'error' ); + } +} diff --git a/includes/Plugin.php b/includes/Plugin.php index 58bf0dc..a53a319 100644 --- a/includes/Plugin.php +++ b/includes/Plugin.php @@ -187,6 +187,9 @@ public function display_flash_notices() { * @return void */ public function init() { + // Load common classes. + new Controllers\ExecutableCodes(); + // Load admin classes. if ( is_admin() ) { new Admin\Admin(); diff --git a/insert-codes.php b/insert-codes.php index 46dd157..1676c75 100644 --- a/insert-codes.php +++ b/insert-codes.php @@ -3,7 +3,7 @@ * Plugin Name: Insert Codes - Headers And Footers Code Snippet * Plugin URI: https://urldev.com/plugins/insert-codes/ * Description: The "Insert Codes - Headers And Footers Code Snippet" plugin allows you to easily add custom code to the header, body, and footer sections of your WordPress website. - * Version: 1.1.0 + * Version: 1.2.0 * Requires at least: 5.0 * Requires PHP: 7.4 * Author: UrlDev @@ -41,7 +41,7 @@ * @return Plugin plugin initialize class. */ function insertcodes() { - return Plugin::create( __FILE__, '1.1.0' ); + return Plugin::create( __FILE__, '1.2.0' ); } // Initialize the plugin. diff --git a/languages/insert-codes.pot b/languages/insert-codes.pot index c14b608..d29c75e 100644 --- a/languages/insert-codes.pot +++ b/languages/insert-codes.pot @@ -2,9 +2,9 @@ # This file is distributed under the GPL v2 or later. msgid "" msgstr "" -"Project-Id-Version: Insert Codes - Headers And Footers Code Snippet 1.0.0\n" +"Project-Id-Version: Insert Codes - Headers And Footers Code Snippet 1.2.0\n" "Report-Msgid-Bugs-To: https://urldev.com/support\n" -"POT-Creation-Date: 2024-08-17 06:32:45+00:00\n" +"POT-Creation-Date: 2024-09-06 18:15:43+00:00\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -29,16 +29,20 @@ msgstr "" msgid "10" msgstr "" -#: includes/Admin/Admin.php:32 includes/Admin/Admin.php:33 +#: includes/Admin/Admin.php:33 includes/Admin/Admin.php:34 #: includes/Admin/views/codes.php:16 msgid "Insert Codes" msgstr "" -#: includes/Admin/Admin.php:43 includes/Admin/Admin.php:44 +#: includes/Admin/Admin.php:44 includes/Admin/Admin.php:45 msgid "Codes" msgstr "" -#: includes/Admin/Admin.php:61 includes/Admin/Admin.php:62 +#: includes/Admin/Admin.php:62 includes/Admin/Admin.php:63 +msgid "Code Snippets" +msgstr "" + +#: includes/Admin/Admin.php:89 includes/Admin/Admin.php:90 #: includes/Admin/views/settings.php:16 msgid "Settings" msgstr "" @@ -76,11 +80,28 @@ msgid "Insert Scripts in Footer:" msgstr "" #: includes/Admin/views/codes.php:70 includes/Admin/views/settings.php:89 -msgid "Recommended Plugins" +#: includes/Admin/views/snippets.php:76 +msgid "Support & Rating" +msgstr "" + +#: includes/Admin/views/codes.php:76 includes/Admin/views/settings.php:95 +#: includes/Admin/views/snippets.php:82 +msgid "If you need help, please visit the support forum." +msgstr "" + +#: includes/Admin/views/codes.php:77 includes/Admin/views/settings.php:96 +#: includes/Admin/views/snippets.php:83 +msgid "Get Support" msgstr "" -#: includes/Admin/views/codes.php:74 includes/Admin/views/settings.php:93 -msgid "Autocomplete Orders for WooCommerce" +#: includes/Admin/views/codes.php:82 includes/Admin/views/settings.php:101 +#: includes/Admin/views/snippets.php:88 +msgid "If you like the plugin, please rate it on WordPress.org." +msgstr "" + +#: includes/Admin/views/codes.php:83 includes/Admin/views/settings.php:102 +#: includes/Admin/views/snippets.php:89 +msgid "Give a Rating" msgstr "" #: includes/Admin/views/settings.php:18 @@ -138,14 +159,87 @@ msgstr "" msgid "Enabling this will delete all the data while uninstalling the plugin." msgstr "" -#: includes/Controllers/Actions.php:41 +#: includes/Admin/views/snippets.php:16 +msgid "Insert Code Snippets" +msgstr "" + +#: includes/Admin/views/snippets.php:18 +msgid "" +"Bellow are the code editor fields for inserting code snippets inside the " +"functions.php file." +msgstr "" + +#: includes/Admin/views/snippets.php:25 +msgid "PHP Code Snippets" +msgstr "" + +#: includes/Admin/views/snippets.php:29 +msgid "PHP Code Snippets:" +msgstr "" + +#: includes/Admin/views/snippets.php:33 +msgid "These scripts will be executed in the PHP context." +msgstr "" + +#: includes/Admin/views/snippets.php:37 +msgid "Snippets Settings" +msgstr "" + +#: includes/Admin/views/snippets.php:41 +msgid "Enable Snippets:" +msgstr "" + +#: includes/Admin/views/snippets.php:46 +msgid "Enable php code snippet" +msgstr "" + +#: includes/Admin/views/snippets.php:48 +msgid "Enabling this will execute the PHP code snippets." +msgstr "" + +#: includes/Admin/views/snippets.php:53 +msgid "Location:" +msgstr "" + +#: includes/Admin/views/snippets.php:57 +msgid "Everywhere" +msgstr "" + +#: includes/Admin/views/snippets.php:58 +msgid "Admin only" +msgstr "" + +#: includes/Admin/views/snippets.php:59 +msgid "Frontend only" +msgstr "" + +#: includes/Admin/views/snippets.php:61 +msgid "Select where the code snippet should execute." +msgstr "" + +#: includes/Controllers/Actions.php:35 includes/Controllers/Actions.php:63 +#: includes/Controllers/Actions.php:99 +msgid "You do not have sufficient permissions to perform this action." +msgstr "" + +#: includes/Controllers/Actions.php:47 msgid "Codes saved successfully." msgstr "" -#: includes/Controllers/Actions.php:66 +#: includes/Controllers/Actions.php:83 +msgid "PHP code snippets saved successfully." +msgstr "" + +#: includes/Controllers/Actions.php:113 msgid "Settings saved successfully." msgstr "" +#: includes/Controllers/Execute.php:168 +msgid "" +"Suspicious code detected. Maybe the code snippet contains a disallowed " +"function: wp_die, die, exit, eval." +msgstr "" + #. Plugin Name of the plugin/theme msgid "Insert Codes - Headers And Footers Code Snippet" msgstr "" diff --git a/package-lock.json b/package-lock.json index 3cd93cf..875a033 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "insert-codes", - "version": "1.0.0", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "insert-codes", - "version": "1.0.0", + "version": "1.2.0", "license": "GPL-2.0-or-later", "devDependencies": { "@lodder/time-grunt": "^4.0.0", diff --git a/package.json b/package.json index 295ff4b..c670e62 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "insert-codes", "title": "Insert Codes - Headers And Footers Code Snippet", - "version": "1.1.0", + "version": "1.2.0", "description": "The \"Insert Codes - Headers And Footers Code Snippet\" plugin allows you to easily add custom code to the header, body, and footer sections of your WordPress website.", "homepage": "https://urldev.com/plugins/insert-codes/", "license": "GPL-2.0-or-later", diff --git a/src/js/insertcodes-admin.js b/src/js/insertcodes-admin.js index 1922d4c..afd068a 100644 --- a/src/js/insertcodes-admin.js +++ b/src/js/insertcodes-admin.js @@ -1,19 +1,42 @@ (function ($) { 'use strict'; $(window).on('load', function () { + // HTML Code Editor. $.ready.then(function () { var defaultSettings = wp.codeEditor.defaultSettings ? _.clone(wp.codeEditor.defaultSettings) : {}; - - // HTML Editor. var htmlSettings = _.extend({}, defaultSettings, { codemirror: _.extend({}, defaultSettings.codemirror, { mode: 'htmlmixed' }) }); - + // Initialize the code editors. wp.codeEditor.initialize($('#insertcodes_header'), htmlSettings); wp.codeEditor.initialize($('#insertcodes_body'), htmlSettings); wp.codeEditor.initialize($('#insertcodes_footer'), htmlSettings); }); + // Php Code Editor. + $.ready.then(function () { + var defaultSettings = wp.codeEditor.defaultSettings ? _.clone(wp.codeEditor.defaultSettings) : {}; + var phpSettings = _.extend({}, defaultSettings, { + codemirror: _.extend({}, defaultSettings.codemirror, { + mode: 'application/x-httpd-php' + }) + }); + // Initialize the code editors. + var phpEditor = wp.codeEditor.initialize($('#insertcodes_php'), phpSettings); + + // include