diff --git a/README.md b/README.md index b91cfa2..bb2688e 100644 --- a/README.md +++ b/README.md @@ -16,32 +16,32 @@ __License:__ [GPL-2.0+](http://www.gnu.org/licenses/gpl-2.0.html) __Plugin page:__ [Knowledge Base](https://webberzone.com/plugins/knowledgebase/) | [WordPress.org Plugin page](https://wordpress.org/plugins/knowledgebase/) -Fastest way to create a highly-flexible multi-product knowledge base on you WordPress site. +Effortlessly build a comprehensive knowledge base for unlimited products on your WordPress site and elevate your customer support experience. ## Description -[Knowledge Base](https://webberzone.com/plugins/knowledgebase/) is an easy to use WordPress plugin that allows you to create a knowledge base / FAQ section on your WordPress blog. +[Knowledge Base](https://webberzone.com/plugins/knowledgebase/) is an easy-to-use WordPress plugin that allows you to create a knowledge base / FAQ section on your site. -This is perfect if you have single or multiple products and want a single knowledge base with little effort. +You can use it to create a single or multi-product knowledge base with little effort. -The plugin was born after I tried several free plugins and themes out there and that couldn't fit my purpose. It's designed to be very easy to install and use out of the box. +The plugin was born after I tried several free plugins and themes that didn't fit my purpose. It's designed to be very easy to install and use out of the box. -You can view a [live demo of my own knowledge base](https://webberzone.com/support/knowledgebase/). +You can view a [live demo of my knowledge base](https://webberzone.com/support/knowledgebase/). ### Terminology * __Articles__: A custom post type `wz_knowledgebase` is used to store all the knowledge base articles -* __Sections__: A custom taxonomy ( `kbcategory` ) used to create the knowledge base. You will need *at least one category* in order to display the knowledge base. These categories can be added under *Knowledge Base > Sections* +* __Sections__: A custom taxonomy ( `kbcategory` ) used to create the knowledge base. You will need *at least one category* to display the knowledge base. Add these categories under *Knowledge Base > Sections* * __Tags__: Additionally you can use tags ( `kbtags` ) can also be used for each knowledge base article. ### Main features * Supports unlimited knowledge bases using different sections with unlimited nested levels -* Inbuilt styles that display the knowledge beautifully and are fully responsive - Uses the [Responsive Grid System](http://www.responsivegridsystem.com/) -* Customizable permalinks: Archives are enabled so your knowledge base can be viewed at `/knowledgebase/` automatically on activation. You can change this in the Settings page -* Shortcode: `[knowledgebase]` will allow you to display the knowledge base on any page of your choosing. For other shortcodes, check the FAQ +* Inbuilt styles that display the Knowledge Base beautifully and are fully responsive - Uses the [Responsive Grid System](http://www.responsivegridsystem.com/) +* Customizable permalinks: Archives are enabled so your knowledge base can be viewed automatically at `/knowledgebase/` upon activation. You can change this on the Settings page +* Shortcode: `[knowledgebase]` will allow you to display the knowledge base on any page you choose. For other shortcodes, check the FAQ * Gutenberg block: You can display the knowledge base using a block. Find it by typing `kb` or `knowledge base` when adding a new block -* Breadcrumbs: Default templates include breadcrumbs. Alternatively, use functions or shortcode to display this where you want +* Breadcrumbs: Default templates include breadcrumbs. Alternatively, use the function or shortcode to display this where you want * Widgets: WZKB Articles, WZKB Sections and WZKB Breadcrumbs * Inbuilt cache to speed up the display of your knowledge base articles @@ -54,15 +54,15 @@ If you have an idea, I'd love to hear it. WebberZone Knowledge Base is also avai ![Knowledge Base Menu in the WordPress Admin](https://raw.githubusercontent.com/WebberZone/knowledgebase/master/wporg-assets/screenshot-1.png) *Knowledge Base Menu in the WordPress Admin* -For more screenshots visit the [WordPress plugin page](http://wordpress.org/plugins/knowledgebase/screenshots/) +For more screenshots, visit the [WordPress plugin page](http://wordpress.org/plugins/knowledgebase/screenshots/) ## Installation ### WordPress install (The easy way) -1. Navigate to “Plugins” within your WordPress Admin Area -2. Click “Add new” and in the search box enter “Knowledgebase” or "Knowledge Base" -3. Find the plugin in the list (usually the first result) and click “Install Now” +1. Navigate to "Plugins" within your WordPress Admin Area +2. Click "Add new" and in the search box enter "Knowledgebase" or "Knowledge Base" +3. Find the plugin in the list (usually the first result) and click "Install Now" 4. Activate or Network activate the Plugin in WP-Admin under the Plugins screen ### Manual install @@ -77,10 +77,10 @@ For more screenshots visit the [WordPress plugin page](http://wordpress.org/plug 2. Visit `Knowledge Base » Add New` to add new Articles to the knowledge base. You can select a section from there while adding 3. Optionally, create a new page or edit an existing one and add the shortcode `[knowledgebase]` or use the block to set up this page to display the knowledgebase -The plugin supports unlimited levels of category hierarchy. To build a multiple product knowledge base: +The plugin supports unlimited levels of category hierarchy. To build a multi-product knowledge base: 1. Set the *First section level* under the Output tab to 2 -2. Create a set of top level sections for each product +2. Create a set of top-level sections for each product 3. Create sub-sections for each of the products [This live demo](https://webberzone.com/support/knowledgebase/) is a working example of a multi-product knowledge base. @@ -89,19 +89,19 @@ The plugin supports unlimited levels of category hierarchy. To build a multiple Check out the [FAQ on the plugin page](http://wordpress.org/plugins/knowledgebase/faq/) and the [Knowledge Base](https://webberzone.com/support/section/knowledgebase/). -If your question isn't listed there, please create a new post at the [WordPress.org support forum](http://wordpress.org/support/plugin/knowledgebase). It is the fastest way to get support as I monitor the forums regularly. I also provide [premium *paid* support via email](https://webberzone.com/support/). +If your question is not listed below, please create a new post at the [WordPress.org support forum](http://wordpress.org/support/plugin/knowledgebase). It is the fastest way to get support, as I monitor the forums regularly. I also provide [premium *paid* support via email](https://webberzone.com/support/). ### 404 errors on the knowledge base -This is usually because of outdated permalinks. To flush the existing permalinks rules simply visit Settings » Permalinks in your WordPress admin area. +This is usually due to outdated permalinks. To flush the existing permalink rules, visit Settings > Permalinks in your WordPress admin area. ### Shortcodes -Refer to [this Knowledge Base article](https://webberzone.com/support/knowledgebase/knowledge-base-shortcodes/) to details of all the shortcodes included in the plugin. +For details on all the shortcodes included in the plugin, refer to [this Knowledge Base article](https://webberzone.com/support/knowledgebase/knowledge-base-shortcodes/). -### Using your own templates for archives and search +### Using your templates for archives and search -WebberZone Knowledge Base comes inbuilt with a set of custom templates to display archives of the articles, category archives as well as search results. You can easily override any of these templates by creating your own template in your theme's folder or in `wp-content/knowledgebase/templates` +WebberZone Knowledge Base comes built with custom templates to display archives of the articles, category archives, and search results. You can easily override any of these templates by creating your template in your theme's folder or in `wp-content/knowledgebase/templates` 1. Article view: single-wz_knowledgebase.php or single-wz_knowledgebase.html 2. Articles archive: archive-wz_knowledgebase.php or archive-wz_knowledgebase.html @@ -118,4 +118,4 @@ You can report security bugs through the Patchstack Vulnerability Disclosure Pro ## About this repository -This GitHub repository always holds the latest development version of the plugin. If you're looking for an official WordPress release, you can find this on the [WordPress.org repository](http://wordpress.org/plugins/knowledgebase). In addition to stable releases, latest beta versions are made available under [releases](https://github.com/WebberZone/knowledgebase/releases). +This GitHub repository always holds the latest development version of the plugin. If you're looking for an official WordPress release, you can find this on the [WordPress.org repository](http://wordpress.org/plugins/knowledgebase). In addition to stable releases, the latest beta versions are made available under [releases](https://github.com/WebberZone/knowledgebase/releases). diff --git a/composer.json b/composer.json index f6f8457..19396ab 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,7 @@ { "name": "webberzone/knowledgebase", "description": "Fastest way to create a highly-flexible multi-product knowledge base.", + "version": "2.3.0", "type": "wordpress-plugin", "keywords": [ "knowledge base", @@ -21,14 +22,14 @@ "php": ">=7.4" }, "require-dev": { - "szepeviktor/phpstan-wordpress": "^1.3", - "phpstan/extension-installer": "^1.3", - "phpstan/phpstan": "^1.10", - "php-stubs/wordpress-stubs": "^6.5", - "wp-coding-standards/wpcs": "^3.0", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7", - "phpcompatibility/phpcompatibility-wp": "^2.1", - "yoast/phpunit-polyfills": "^1.0", + "szepeviktor/phpstan-wordpress": "^1", + "phpstan/extension-installer": "^1", + "phpstan/phpstan": "^1", + "php-stubs/wordpress-stubs": "^6", + "wp-coding-standards/wpcs": "^3", + "dealerdirect/phpcodesniffer-composer-installer": "^1", + "phpcompatibility/phpcompatibility-wp": "^2", + "yoast/phpunit-polyfills": "^3", "phpunit/phpunit": "^5.7.21 || ^6.5 || ^7.5" }, "config": { diff --git a/includes/activate-deactivate.php b/includes/activate-deactivate.php deleted file mode 100644 index f6c9003..0000000 --- a/includes/activate-deactivate.php +++ /dev/null @@ -1,132 +0,0 @@ -get_col( //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching - " - SELECT blog_id FROM $wpdb->blogs - WHERE archived = '0' AND spam = '0' AND deleted = '0' - " - ); - - foreach ( $blog_ids as $blog_id ) { - switch_to_blog( $blog_id ); - wzkb_single_activate(); - restore_current_blog(); - } - } else { - wzkb_single_activate(); - } -} -register_activation_hook( WZKB_PLUGIN_FILE, 'wzkb_plugin_activate' ); - - -/** - * Runs on Plugin activation. - * - * @since 1.1.0 - */ -function wzkb_single_activate() { - - // Register types to register the rewrite rules. - wzkb_register_post_type(); - - // Then flush them. - global $wp_rewrite; - $wp_rewrite->init(); - flush_rewrite_rules( false ); -} - - -/** - * Fired when a new site is activated with a WPMU environment. - * - * @since 2.0.0 - * - * @param int|WP_Site $blog WordPress 5.1 passes a WP_Site object. - */ -function wzkb_activate_new_site( $blog ) { - - if ( ! is_plugin_active_for_network( plugin_basename( WZKB_PLUGIN_FILE ) ) ) { - return; - } - - if ( ! is_int( $blog ) ) { - $blog = $blog->id; - } - - switch_to_blog( $blog ); - wzkb_single_activate(); - restore_current_blog(); -} -if ( version_compare( get_bloginfo( 'version' ), '5.1', '>=' ) ) { - add_action( 'wp_initialize_site', 'wzkb_activate_new_site' ); -} else { - add_action( 'wpmu_new_blog', 'wzkb_activate_new_site' ); -} - - -/** - * Runs on Plugin deactivation. - * - * @since 1.0.0 - * - * @param bool $network_wide Network wide flag. - */ -function wzkb_plugin_deactivate( $network_wide ) { - - global $wpdb; - - if ( is_multisite() && $network_wide ) { - - // Get all blogs in the network and activate plugin on each one. - $blog_ids = $wpdb->get_col( //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching - " - SELECT blog_id FROM $wpdb->blogs - WHERE archived = '0' AND spam = '0' AND deleted = '0' - " - ); - - foreach ( $blog_ids as $blog_id ) { - switch_to_blog( $blog_id ); - global $wp_rewrite; - $wp_rewrite->init(); - flush_rewrite_rules(); - } - - // Switch back to the current blog. - restore_current_blog(); - - } - - flush_rewrite_rules(); -} -register_deactivation_hook( WZKB_PLUGIN_FILE, 'wzkb_plugin_deactivate' ); diff --git a/includes/admin/admin.php b/includes/admin/admin.php deleted file mode 100644 index 26bf3cc..0000000 --- a/includes/admin/admin.php +++ /dev/null @@ -1,140 +0,0 @@ - 'ID', - ); - - return array_merge( $columns, $new_columns ); -} -add_filter( 'manage_edit-wzkb_category_columns', 'wzkb_tax_columns' ); -add_filter( 'manage_edit-wzkb_category_sortable_columns', 'wzkb_tax_columns' ); -add_filter( 'manage_edit-wzkb_tag_columns', 'wzkb_tax_columns' ); -add_filter( 'manage_edit-wzkb_tag_sortable_columns', 'wzkb_tax_columns' ); - - -/** - * Add taxonomy ID to the admin column. - * - * @since 1.0.0 - * - * @param string $value Deprecated. - * @param string $name Name of the column. - * @param int|string $id Category ID. - * @return int|string - */ -function wzkb_tax_id( $value, $name, $id ) { - return 'tax_id' === $name ? $id : $value; -} -add_filter( 'manage_wzkb_category_custom_column', 'wzkb_tax_id', 10, 3 ); -add_filter( 'manage_wzkb_tag_custom_column', 'wzkb_tax_id', 10, 3 ); - -/** - * Filters Admin Notices to add a notice when the settings are not saved. - * - * @since 1.2.0 - * @return void - */ -function wzkb_admin_notices() { - - $kbslug = wzkb_get_option( 'kb_slug', 'not-set-random-string' ); - $catslug = wzkb_get_option( 'category_slug', 'not-set-random-string' ); - $tagslug = wzkb_get_option( 'tag_slug', 'not-set-random-string' ); - - // Only add the notice if the user is an admin. - if ( ! current_user_can( 'manage_options' ) ) { - return; - } - - // Only add the notice if the settings cannot be found. - if ( 'not-set-random-string' === $kbslug || 'not-set-random-string' === $catslug || 'not-set-random-string' === $tagslug ) { - ?> - -
-

- admin page to update and save the options.', 'knowledgebase' ), esc_url( admin_url( 'edit.php?post_type=wz_knowledgebase&page=wzkb-settings' ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - ?> -

-
- - publish ) ) { - /* translators: 1. Number of articles */ - $text = _n( '%s KB article', '%s KB articles', $num_posts->publish, 'knowledgebase' ); - - $text = sprintf( $text, number_format_i18n( $num_posts->publish ) ); - - if ( current_user_can( 'edit_posts' ) ) { - $text = sprintf( '%1$s', $text ); - } else { - $text = sprintf( '%1$s', $text ); - } - - $items[] = $text; - } - - return $items; -} -add_filter( 'dashboard_glance_items', 'wzkb_dashboard_glance_items', 1 ); - - -/** - * Add CSS to Admin head - * - * @since 1.5.0 - * - * return void - */ -function wzkb_admin_head() { - ?> - - 0, + 'spam' => 0, + 'deleted' => 0, + ) + ); + + foreach ( $sites as $site ) { + switch_to_blog( (int) $site->blog_id ); + self::single_activate(); + } + + // Switch back to the current blog. + restore_current_blog(); + + } else { + self::single_activate(); + } + } + + /** + * Activates the plugin on a new site. + * + * @since 3.3.0 + * + * @param int|\WP_Site $blog The blog ID. + */ + public function activate_new_site( $blog ) { + if ( ! is_plugin_active_for_network( plugin_basename( WZKB_PLUGIN_FILE ) ) ) { + return; + } + + if ( ! is_int( $blog ) ) { + $blog = $blog->id; + } + + switch_to_blog( $blog ); + self::single_activate(); + restore_current_blog(); + } + + /** + * Activation method. + * + * @since 2.3.0 + */ + public static function single_activate() { + // Register types to register the rewrite rules. + CPT::register_post_type(); + CPT::register_taxonomies(); + + // Then flush them. + global $wp_rewrite; + $wp_rewrite->init(); + flush_rewrite_rules( false ); + } + + /** + * Deactivation method. + * + * @since 2.3.0 + * + * @param boolean $network_wide True if WPMU superadmin uses + * "Network Activate" action, false if + * WPMU is disabled or plugin is + * activated on an individual blog. + */ + public static function deactivate( $network_wide ) { + if ( is_multisite() && $network_wide ) { + $sites = get_sites( + array( + 'archived' => 0, + 'spam' => 0, + 'deleted' => 0, + ) + ); + + foreach ( $sites as $site ) { + switch_to_blog( (int) $site->blog_id ); + global $wp_rewrite; + $wp_rewrite->init(); + flush_rewrite_rules(); + restore_current_blog(); + } + } + + // Flush the rewrite rules. + global $wp_rewrite; + $wp_rewrite->init(); + flush_rewrite_rules(); + } +} diff --git a/includes/admin/class-admin-columns.php b/includes/admin/class-admin-columns.php new file mode 100644 index 0000000..fa85579 --- /dev/null +++ b/includes/admin/class-admin-columns.php @@ -0,0 +1,72 @@ + 'ID', + ); + + return array_merge( $columns, $new_columns ); + } + + /** + * Add taxonomy ID to the admin column. + * + * @since 2.3.0 + * + * @param string $value Deprecated. + * @param string $name Name of the column. + * @param int|string $id Category ID. + * @return int|string + */ + public static function tax_id( $value, $name, $id ) { + return 'tax_id' === $name ? $id : $value; + } +} diff --git a/includes/admin/class-admin.php b/includes/admin/class-admin.php new file mode 100644 index 0000000..5098217 --- /dev/null +++ b/includes/admin/class-admin.php @@ -0,0 +1,256 @@ +hooks(); + + // Initialise admin classes. + $this->settings = new Settings\Settings(); + $this->activator = new Activator(); + $this->cache = new Cache(); + $this->admin_columns = new Admin_Columns(); + } + + /** + * Run the hooks. + * + * @since 2.3.0 + */ + public function hooks() { + add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) ); + add_action( 'admin_notices', array( $this, 'admin_notices' ) ); + add_filter( 'dashboard_glance_items', array( $this, 'dashboard_glance_items' ), 10, 1 ); + add_filter( 'admin_head', array( $this, 'admin_head' ) ); + add_action( 'admin_footer', array( $this, 'maybe_add_button_to_post_list' ) ); + } + + /** + * Enqueue scripts in admin area. + * + * @since 2.3.0 + */ + public function admin_enqueue_scripts() { + + $minimize = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; + + wp_register_script( + 'wzkb-admin-js', + plugins_url( 'js/admin-scripts' . $minimize . '.js', __FILE__ ), + array( 'jquery', 'jquery-ui-tabs' ), + WZKB_VERSION, + true + ); + wp_localize_script( + 'wzkb-admin-js', + 'wzkb_admin', + array( + 'nonce' => wp_create_nonce( 'wzkb_admin_nonce' ), + ) + ); + wp_register_style( + 'wzkb-admin-ui-css', + plugins_url( 'css/admin' . $minimize . '.css', __FILE__ ), + array(), + WZKB_VERSION + ); + + if ( isset( $_GET['post_type'] ) && 'wz_knowledgebase' === $_GET['post_type'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended + wp_enqueue_style( 'wzkb-admin-ui-css' ); + } + } + + /** + * Display admin notices. + * + * @since 2.3.0 + */ + public function admin_notices() { + $kbslug = \wzkb_get_option( 'kb_slug', 'not-set-random-string' ); + $catslug = \wzkb_get_option( 'category_slug', 'not-set-random-string' ); + $tagslug = \wzkb_get_option( 'tag_slug', 'not-set-random-string' ); + + // Only add the notice if the user is an admin. + if ( ! current_user_can( 'manage_options' ) ) { + return; + } + + // Only add the notice if the settings cannot be found. + if ( 'not-set-random-string' === $kbslug || 'not-set-random-string' === $catslug || 'not-set-random-string' === $tagslug ) { + ?> +
+

+ admin page to update and save the options.', 'knowledgebase' ), esc_url( admin_url( 'edit.php?post_type=wz_knowledgebase&page=wzkb-settings' ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ?> +

+
+ publish ) ) { + /* translators: 1. Number of articles */ + $text = _n( '%s KB article', '%s KB articles', $num_posts->publish, 'knowledgebase' ); + + $text = sprintf( $text, number_format_i18n( $num_posts->publish ) ); + + if ( current_user_can( 'edit_posts' ) ) { + $text = sprintf( '%1$s', $text ); + } else { + $text = sprintf( '%1$s', $text ); + } + + $items[] = $text; + } + + return $items; + } + + /** + * Add CSS to Admin head + * + * @since 2.3.0 + * + * return void + */ + public function admin_head() { + if ( 'index.php' === $GLOBALS['pagenow'] ) { + ?> + + post_type && 'wzkb_category' !== $screen->taxonomy ) || + ( 'edit' !== $screen->base && 'wz_knowledgebase' !== $screen->post_type ) ) { + return; + } + + $this->render_custom_button( 'Visit Knowledge Base' ); + } + + /** + * Render the custom button + * + * @since 2.3.0 + * + * @param string $button_text Text to display on the button. + */ + private function render_custom_button( $button_text = 'Custom Action' ) { + ?> + + '); + clearCache($button); + } + }); + + // Function to clear the cache. + function clearCache($button) { + $.post(wzkb_admin_data.ajax_url, { + action: 'wzkb_clear_cache', + security: wzkb_admin_data.security + }, function (response) { + if (response.success) { + alert(response.data.message); + } else { + alert(wzkb_admin_data.fail_message); + } + }).fail(function (jqXHR, textStatus) { + alert(wzkb_admin_data.request_fail_message + textStatus); + }).always(function () { + $button.prop('disabled', false).find('.spinner').remove(); + }); + } + + // Prompt the user when they leave the page without saving the form. + var formmodified = 0; + + function confirmFormChange() { + formmodified = 1; + } + + function confirmExit() { + if (formmodified == 1) { + return true; + } + } + + function formNotModified() { + formmodified = 0; + } + + $('form *').change(confirmFormChange); + + window.onbeforeunload = confirmExit; + + $("input[name='submit']").click(formNotModified); + $("input[id='search-submit']").click(formNotModified); + $("input[id='doaction']").click(formNotModified); + $("input[id='doaction2']").click(formNotModified); + $("input[name='filter_action']").click(formNotModified); + + $( + function () { + $("#post-body-content").tabs( + { + create: function (event, ui) { + $(ui.tab.find("a")).addClass("nav-tab-active"); + }, + activate: function (event, ui) { + $(ui.oldTab.find("a")).removeClass("nav-tab-active"); + $(ui.newTab.find("a")).addClass("nav-tab-active"); + } + } + ); + } + ); + } +); diff --git a/includes/admin/js/admin-scripts.min.js b/includes/admin/js/admin-scripts.min.js new file mode 100644 index 0000000..204169a --- /dev/null +++ b/includes/admin/js/admin-scripts.min.js @@ -0,0 +1 @@ +jQuery(document).ready((function(a){a('button[name="wzkb_cache_clear"]').on("click",(function(){if(confirm(wzkb_admin_data.confirm_message)){var n=a(this);n.prop("disabled",!0).append(' '),function(n){a.post(wzkb_admin_data.ajax_url,{action:"wzkb_clear_cache",security:wzkb_admin_data.security},(function(a){a.success?alert(a.data.message):alert(wzkb_admin_data.fail_message)})).fail((function(a,n){alert(wzkb_admin_data.request_fail_message+n)})).always((function(){n.prop("disabled",!1).find(".spinner").remove()}))}(n)}}));var n=0;function i(){n=0}a("form *").change((function(){n=1})),window.onbeforeunload=function(){if(1==n)return!0},a("input[name='submit']").click(i),a("input[id='search-submit']").click(i),a("input[id='doaction']").click(i),a("input[id='doaction2']").click(i),a("input[name='filter_action']").click(i),a((function(){a("#post-body-content").tabs({create:function(n,i){a(i.tab.find("a")).addClass("nav-tab-active")},activate:function(n,i){a(i.oldTab.find("a")).removeClass("nav-tab-active"),a(i.newTab.find("a")).addClass("nav-tab-active")}})}))})); \ No newline at end of file diff --git a/includes/admin/modules/cache.php b/includes/admin/modules/cache.php deleted file mode 100644 index fbec6a7..0000000 --- a/includes/admin/modules/cache.php +++ /dev/null @@ -1,100 +0,0 @@ -query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching - $wpdb->prepare( - " - DELETE FROM {$wpdb->termmeta} - WHERE meta_key = %s - ", - $meta_key - ) - ); - } -} - - -/** - * Get the default meta keys used for the cache - * - * @since 1.8.0 - */ -function wzkb_cache_get_keys() { - - $meta_keys = wzkb_cache_get_meta_keys(); - - /** - * Filters the array containing the various cache keys. - * - * @since 1.8.0 - * - * @param array $default_meta_keys Array of meta keys - */ - return apply_filters( 'wzkb_cache_keys', $meta_keys ); -} - - -/** - * Get the _wzkb_cache keys. - * - * @since 1.8.0 - * - * @return array Array of _wzkb_cache keys. - */ -function wzkb_cache_get_meta_keys() { - global $wpdb; - - $keys = array(); - - $sql = " - SELECT meta_key - FROM {$wpdb->termmeta} - WHERE `meta_key` LIKE '_wzkb_cache_%' - "; - - $results = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared - - $keys = wp_list_pluck( $results, 'meta_key' ); - - /** - * Filter the array of _wzkb_cache keys. - * - * @since 1.8.0 - * - * @return array Array of _wzkb_cache keys. - */ - return apply_filters( 'wzkb_cache_get_meta_keys', $keys ); -} diff --git a/includes/admin/settings/class-knowledgebase-settings.php b/includes/admin/settings/class-knowledgebase-settings.php deleted file mode 100644 index 18d26c6..0000000 --- a/includes/admin/settings/class-knowledgebase-settings.php +++ /dev/null @@ -1,655 +0,0 @@ -settings_key = 'wzkb_settings'; - self::$prefix = 'wzkb'; - $this->menu_slug = 'wzkb-settings'; - - $props = array( - 'menu_type' => 'submenu', - 'parent_slug' => 'edit.php?post_type=wz_knowledgebase', - 'menu_slug' => $this->menu_slug, - 'default_tab' => 'general', - 'help_sidebar' => $this->get_help_sidebar(), - 'help_tabs' => $this->get_help_tabs(), - 'admin_footer_text' => $this->get_admin_footer_text(), - ); - - $args = array( - 'translation_strings' => $this->get_translation_strings(), - 'props' => $props, - 'settings_sections' => $this->get_settings_sections(), - 'registered_settings' => $this->get_registered_settings(), - 'upgraded_settings' => array(), - ); - - $this->settings_api = new Knowledgebase_Admin\Settings_API( $this->settings_key, self::$prefix, $args ); - - add_action( 'admin_head', array( $this, 'admin_head' ), 11 ); - add_filter( 'plugin_row_meta', array( $this, 'plugin_row_meta' ), 10, 2 ); - add_filter( 'plugin_action_links_' . plugin_basename( WZKB_PLUGIN_FILE ), array( $this, 'plugin_actions_links' ) ); - add_action( 'wzkb_settings_page_header', array( $this, 'settings_page_header' ), 11 ); - add_filter( 'wzkb_settings_sanitize', array( $this, 'change_settings_on_save' ), 99 ); - } - - /** - * Singleton instance - * - * @since 2.2.0 - */ - public static function get_instance() { - if ( ! isset( self::$instance ) ) { - self::$instance = new self(); - } - return self::$instance; - } - - /** - * Array containing the various strings used by the Settings_API. - * - * @since 2.2.0 - * - * @return array Settings array - */ - public function get_translation_strings() { - $strings = array( - 'page_title' => esc_html__( 'Knowledge Base Settings', 'knowledgebase' ), - 'menu_title' => esc_html__( 'Settings', 'knowledgebase' ), - 'page_header' => esc_html__( 'Knowledge Base Settings', 'knowledgebase' ), - 'reset_message' => esc_html__( 'Settings have been reset to their default values. Reload this page to view the updated settings.', 'knowledgebase' ), - 'success_message' => esc_html__( 'Settings updated.', 'knowledgebase' ), - 'save_changes' => esc_html__( 'Save Changes', 'knowledgebase' ), - 'reset_settings' => esc_html__( 'Reset all settings', 'knowledgebase' ), - 'reset_button_confirm' => esc_html__( 'Do you really want to reset all these settings to their default values?', 'knowledgebase' ), - 'checkbox_modified' => esc_html__( 'Modified from default setting', 'knowledgebase' ), - ); - - /** - * Filter the array containing the settings' sections. - * - * @since 2.2.0 - * - * @param array $strings Translation strings. - */ - return apply_filters( self::$prefix . '_translation_strings', $strings ); - } - - /** - * Array containing the settings' sections. - * - * @since 2.2.0 - * - * @return array Array of settings' sections. - */ - public function get_settings_sections() { - $sections = array( - 'general' => __( 'General', 'knowledgebase' ), - 'output' => __( 'Output', 'knowledgebase' ), - 'styles' => __( 'Styles', 'knowledgebase' ), - ); - - /** - * Filter the array containing the settings' sections. - * - * @since 2.2.0 - * - * @param array $sections Array of settings' sections - */ - return apply_filters( self::$prefix . '_settings_sections', $sections ); - } - - - /** - * Retrieve the array of plugin settings - * - * @since 2.2.0 - * - * @return array Settings array - */ - public static function get_registered_settings() { - - $settings = array( - 'general' => self::settings_general(), - 'output' => self::settings_output(), - 'styles' => self::settings_styles(), - ); - - /** - * Filters the settings array - * - * @since 2.2.0 - * - * @param array $Knowledgebase_setings Settings array - */ - return apply_filters( self::$prefix . '_registered_settings', $settings ); - } - - /** - * Returns the Header settings. - * - * @since 2.2.0 - * - * @return array Header settings. - */ - public static function settings_general() { - - $settings = array( - 'slug_header' => array( - 'id' => 'slug_header', - 'name' => '

' . esc_html__( 'Slug options', 'knowledgebase' ) . '

', - 'desc' => '', - 'type' => 'header', - ), - 'kb_slug' => array( - 'id' => 'kb_slug', - 'name' => esc_html__( 'Knowledge Base slug', 'knowledgebase' ), - 'desc' => esc_html__( 'This will set the opening path of the URL of the knowledge base and is set when registering the custom post type', 'knowledgebase' ), - 'type' => 'text', - 'options' => 'knowledgebase', - ), - 'category_slug' => array( - 'id' => 'category_slug', - 'name' => esc_html__( 'Category slug', 'knowledgebase' ), - 'desc' => esc_html__( 'Each category is a section of the knowledge base. This setting is used when registering the custom category and forms a part of the URL when browsing category archives', 'knowledgebase' ), - 'type' => 'text', - 'options' => 'section', - ), - 'tag_slug' => array( - 'id' => 'tag_slug', - 'name' => esc_html__( 'Tag slug', 'knowledgebase' ), - 'desc' => esc_html__( 'Each article can have multiple tags. This setting is used when registering the custom tag and forms a part of the URL when browsing tag archives', 'knowledgebase' ), - 'type' => 'text', - 'options' => 'kb-tags', - ), - 'cache' => array( - 'id' => 'cache', - 'name' => esc_html__( 'Enable cache', 'knowledgebase' ), - 'desc' => esc_html__( 'Cache the output of the WP_Query lookups to speed up retrieval of the knowledgebase. Recommended for large knowledge bases', 'knowledgebase' ), - 'type' => 'checkbox', - 'options' => false, - ), - 'uninstall_header' => array( - 'id' => 'uninstall_header', - 'name' => '

' . esc_html__( 'Uninstall options', 'knowledgebase' ) . '

', - 'desc' => '', - 'type' => 'header', - 'options' => '', - ), - 'uninstall_options' => array( - 'id' => 'uninstall_options', - 'name' => esc_html__( 'Delete options on uninstall', 'knowledgebase' ), - 'desc' => esc_html__( 'Check this box to delete the settings on this page when the plugin is deleted via the Plugins page in your WordPress Admin', 'knowledgebase' ), - 'type' => 'checkbox', - 'options' => true, - ), - 'uninstall_data' => array( - 'id' => 'uninstall_data', - 'name' => esc_html__( 'Delete all knowledge base posts on uninstall', 'knowledgebase' ), - 'desc' => esc_html__( 'Check this box to delete all the posts, categories and tags created by the plugin. There is no way to restore the data if you choose this option', 'knowledgebase' ), - 'type' => 'checkbox', - 'options' => false, - ), - 'feed_header' => array( - 'id' => 'feed_header', - 'name' => '

' . esc_html__( 'Feed options', 'knowledgebase' ) . '

', - 'desc' => '', - 'type' => 'header', - 'options' => '', - ), - 'include_in_feed' => array( - 'id' => 'include_in_feed', - 'name' => esc_html__( 'Include in feed', 'knowledgebase' ), - 'desc' => esc_html__( 'Adds the knowledge base articles to the main RSS feed for your site', 'knowledgebase' ), - 'type' => 'checkbox', - 'options' => true, - ), - 'disable_kb_feed' => array( - 'id' => 'disable_kb_feed', - 'name' => esc_html__( 'Disable KB feed', 'knowledgebase' ), - /* translators: 1: Opening link tag, 2: Closing link tag. */ - 'desc' => sprintf( esc_html__( 'The knowledge base articles have a default feed. This option will disable the feed. You might need to %1$srefresh your permalinks%2$s when changing this option.', 'knowledgebase' ), '', '' ), - 'type' => 'checkbox', - 'options' => false, - ), - ); - - /** - * Filters the Header settings array - * - * @since 2.2.0 - * - * @param array $settings Header Settings array - */ - return apply_filters( self::$prefix . '_settings_general', $settings ); - } - - /** - * Returns the Pingbacks/Trackbacks settings. - * - * @since 2.2.0 - * - * @return array Pingbacks/Trackbacks settings. - */ - public static function settings_output() { - - $settings = array( - 'kb_title' => array( - 'id' => 'kb_title', - 'name' => esc_html__( 'Knowledge base title', 'knowledgebase' ), - 'desc' => esc_html__( 'This will be displayed as the title of the archive title as well as on other relevant places.', 'knowledgebase' ), - 'type' => 'text', - 'options' => 'Knowledge Base', - 'field_class' => 'large-text', - ), - 'category_level' => array( - 'id' => 'category_level', - 'name' => esc_html__( 'First section level', 'knowledgebase' ), - 'desc' => esc_html__( 'This option allows you to create multi-level knowledge bases. This works in conjunction with the inbuilt styles. Set to 1 to lay out the top level sections in a grid. Set to 2 to lay out the second level categories in the grid. This is great if you have multiple products and want to create separate knowledge bases for each of them. The default option is 2 and was the behaviour of this plugin before v1.5.0.', 'knowledgebase' ), - 'type' => 'number', - 'options' => '2', - 'size' => 'small', - 'min' => '1', - 'max' => '5', - ), - 'show_article_count' => array( - 'id' => 'show_article_count', - 'name' => esc_html__( 'Show article count', 'knowledgebase' ), - 'desc' => esc_html__( 'If selected, the number of articles will be displayed in an orange circle next to the header. You can override the color by styling wzkb_section_count', 'knowledgebase' ), - 'type' => 'checkbox', - 'options' => false, - ), - 'show_excerpt' => array( - 'id' => 'show_excerpt', - 'name' => esc_html__( 'Show excerpt', 'knowledgebase' ), - 'desc' => esc_html__( 'Select to include the post excerpt after the article link', 'knowledgebase' ), - 'type' => 'checkbox', - 'options' => false, - ), - 'clickable_section' => array( - 'id' => 'clickable_section', - 'name' => esc_html__( 'Link section title', 'knowledgebase' ), - 'desc' => esc_html__( 'If selected, the title of each section of the knowledgebase will be linked to its own page', 'knowledgebase' ), - 'type' => 'checkbox', - 'options' => true, - ), - 'show_empty_sections' => array( - 'id' => 'show_empty_sections', - 'name' => esc_html__( 'Show empty sections', 'knowledgebase' ), - 'desc' => esc_html__( 'If selected, sections with no articles will also be displayed', 'knowledgebase' ), - 'type' => 'checkbox', - 'options' => false, - ), - 'limit' => array( - 'id' => 'limit', - 'name' => esc_html__( 'Max articles per section', 'knowledgebase' ), - 'desc' => esc_html__( 'Enter the number of articles that should be displayed in each section when viewing the knowledge base. After this limit is reached, the footer is displayed with the more link to view the category.', 'knowledgebase' ), - 'type' => 'number', - 'options' => '5', - 'size' => 'small', - 'min' => '1', - 'max' => '500', - ), - 'show_sidebar' => array( - 'id' => 'show_sidebar', - 'name' => esc_html__( 'Show sidebar', 'knowledgebase' ), - 'desc' => esc_html__( 'Add the sidebar of your theme into the inbuilt templates for archive, sections and search. Activate this option if your theme does not already include this.', 'knowledgebase' ), - 'type' => 'checkbox', - 'options' => false, - ), - 'show_related_articles' => array( - 'id' => 'show_related_articles', - 'name' => esc_html__( 'Show related articles', 'knowledgebase' ), - 'desc' => esc_html__( 'Add related articles at the bottom of the knowledge base article. Only works when using the inbuilt template.', 'knowledgebase' ), - 'type' => 'checkbox', - 'options' => true, - ), - ); - - /** - * Filters the Pingbacks/Trackbacks settings array - * - * @since 2.2.0 - * - * @param array $settings Pingbacks/Trackbacks Settings array - */ - return apply_filters( self::$prefix . '_settings_output', $settings ); - } - - /** - * Returns the Comments settings. - * - * @since 2.2.0 - * - * @return array Comments settings. - */ - public static function settings_styles() { - - $settings = array( - 'include_styles' => array( - 'id' => 'include_styles', - 'name' => esc_html__( 'Include inbuilt styles', 'knowledgebase' ), - 'desc' => esc_html__( 'Uncheck this to disable this plugin from adding the inbuilt styles. You will need to add your own CSS styles if you disable this option', 'knowledgebase' ), - 'type' => 'checkbox', - 'options' => true, - ), - 'columns' => array( - 'id' => 'columns', - 'name' => esc_html__( 'Number of columns', 'knowledgebase' ), - 'desc' => esc_html__( 'Set number of columns to display the knowledge base archives. This is only works if the above option is selected.', 'knowledgebase' ), - 'type' => 'number', - 'options' => '2', - 'size' => 'small', - 'min' => '1', - 'max' => '5', - ), - 'custom_css' => array( - 'id' => 'custom_css', - 'name' => esc_html__( 'Custom CSS', 'knowledgebase' ), - 'desc' => esc_html__( 'Enter any custom valid CSS without any wrapping <style> tags', 'knowledgebase' ), - 'type' => 'css', - 'options' => '', - 'field_class' => 'codemirror_css', - ), - ); - - /** - * Filters the Comments settings array - * - * @since 2.2.0 - * - * @param array $settings Comments Settings array - */ - return apply_filters( self::$prefix . '_settings_styles', $settings ); - } - - /** - * Adding WordPress plugin action links. - * - * @since 2.2.0 - * - * @param array $links Array of links. - * @return array - */ - public function plugin_actions_links( $links ) { - - return array_merge( - array( - 'settings' => '' . esc_html__( 'Settings', 'knowledgebase' ) . '', - ), - $links - ); - } - - /** - * Add meta links on Plugins page. - * - * @since 2.2.0 - * - * @param array $links Array of Links. - * @param string $file Current file. - * @return array - */ - public function plugin_row_meta( $links, $file ) { - - if ( false !== strpos( $file, 'knowledgebase.php' ) ) { - $new_links = array( - 'support' => '' . esc_html__( 'Support', 'knowledgebase' ) . '', - ); - - $links = array_merge( $links, $new_links ); - } - return $links; - } - - /** - * Get the help sidebar content to display on the plugin settings page. - * - * @since 2.2.0 - */ - public function get_help_sidebar() { - - $help_sidebar = - /* translators: 1: Plugin support site link. */ - '

' . sprintf( __( 'For more information or how to get support visit the support site.', 'knowledgebase' ), esc_url( 'https://webberzone.com/support/' ) ) . '

' . - /* translators: 1: WordPress.org support forums link. */ - '

' . sprintf( __( 'Support queries should be posted in the WordPress.org support forums.', 'knowledgebase' ), esc_url( 'https://wordpress.org/support/plugin/knowledgebase' ) ) . '

' . - '

' . sprintf( - /* translators: 1: Github issues link, 2: Github plugin page link. */ - __( 'Post an issue on GitHub (bug reports only).', 'knowledgebase' ), - esc_url( 'https://github.com/ajaydsouza/knowledgebase/issues' ), - esc_url( 'https://github.com/ajaydsouza/knowledgebase' ) - ) . '

'; - - /** - * Filter to modify the help sidebar content. - * - * @since 2.2.0 - * - * @param string $help_sidebar Help sidebar content. - */ - return apply_filters( self::$prefix . '_settings_help_sidebar', $help_sidebar ); - } - - /** - * Get the help tabs to display on the plugin settings page. - * - * @since 2.2.0 - */ - public function get_help_tabs() { - - $help_tabs = array( - array( - 'id' => 'wzkb-settings-general', - 'title' => __( 'General', 'knowledgebase' ), - 'content' => - '

' . __( 'This screen provides the basic settings for configuring your knowledge base.', 'knowledgebase' ) . '

' . - '

' . __( 'Set the knowledge base slugs which drive what the urls are for the knowledge base homepage, articles, categories and tags.', 'knowledgebase' ) . '

', - ), - array( - 'id' => 'wzkb-settings-styles', - 'title' => __( 'Styles', 'knowledgebase' ), - 'content' => - '

' . __( 'This screen provides options to control the look and feel of the knowledge base.', 'knowledgebase' ) . '

' . - '

' . __( 'Disable the styles included within the plugin and/or add your own CSS styles to customize this.', 'knowledgebase' ) . '

', - ), - ); - - /** - * Filter to add more help tabs. - * - * @since 2.2.0 - * - * @param array $help_tabs Associative array of help tabs. - */ - return apply_filters( self::$prefix . '_settings_help_tabs', $help_tabs ); - } - - /** - * Function to add contextual help in the Tools page. - * - * @since 2.2.0 - */ - public function settings_help() { - $screen = get_current_screen(); - - if ( $screen->id === $this->tools_page ) { - - $screen->set_help_sidebar( $this->get_help_sidebar() ); - - $screen->add_help_tab( - array( - 'id' => 'wzkb-tools-general', - 'title' => __( 'Tools', 'knowledgebase' ), - 'content' => - '

' . __( 'This screen gives you a few tools namely one click buttons to run the closing algorithm or open comments, pingbacks/trackbacks.', 'knowledgebase' ) . '

' . - '

' . __( 'You can also delete the old settings from prior to v2.0.0', 'knowledgebase' ) . '

', - ) - ); - } - } - - /** - * Add CSS to admin head. - * - * @since 2.2.0 - */ - public function admin_head() { - if ( ! is_customize_preview() ) { - $css = ' - '; - - echo $css; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } - } - - /** - * Function to add a link below the page header of the Settings page. - * - * @since 2.2.0 - */ - public function settings_page_header() { - ?> -

- - - -

- ', - '', - '' - ); - } - - /** - * Modify settings when they are being saved. - * - * @since 2.2.0 - * - * @param array $settings Settings array. - * @return array $settings Sanitized settings array. - */ - public function change_settings_on_save( $settings ) { - - // Delete the cache. - wzkb_cache_delete(); - - return $settings; - } - } - - /** Initiate the class */ - add_action( - 'init', - function () { - Knowledgebase_Settings::get_instance(); - }, - 999 - ); - -endif; diff --git a/includes/admin/settings/class-metabox-api.php b/includes/admin/settings/class-metabox-api.php new file mode 100644 index 0000000..c635466 --- /dev/null +++ b/includes/admin/settings/class-metabox-api.php @@ -0,0 +1,352 @@ + '', + 'prefix' => '', + 'post_type' => '', + 'title' => '', + 'registered_settings' => array(), + 'checkbox_modified_text' => '', + ); + + $args = wp_parse_args( $args, $defaults ); + + foreach ( $args as $name => $value ) { + $this->$name = $value; + } + + add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) ); + add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) ); + add_action( "save_post_{$this->post_type}", array( $this, 'save' ) ); + } + + /** + * Function to add the metabox. + */ + public function add_meta_boxes() { + add_meta_box( + $this->prefix . '_metabox_id', + $this->title, + array( $this, 'html' ), + $this->post_type, + 'advanced', + 'high' + ); + } + + /** + * Enqueue scripts and styles. + * + * @param string $hook The current admin page. + */ + public function admin_enqueue_scripts( $hook ) { + if ( in_array( $hook, array( 'post.php', 'post-new.php' ), true ) || get_current_screen()->post_type === $this->post_type ) { + self::enqueue_scripts_styles(); + } + } + + /** + * Enqueues all scripts, styles, settings, and templates necessary to use the Settings API. + */ + public static function enqueue_scripts_styles() { + + $minimize = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; + + wp_enqueue_style( 'wp-color-picker' ); + + wp_enqueue_media(); + wp_enqueue_script( 'wp-color-picker' ); + wp_enqueue_script( 'jquery' ); + wp_enqueue_script( 'jquery-ui-autocomplete' ); + wp_enqueue_script( 'jquery-ui-tabs' ); + + wp_enqueue_code_editor( + array( + 'type' => 'text/html', + 'codemirror' => array( + 'indentUnit' => 2, + 'tabSize' => 2, + ), + ) + ); + + // Enqueue WZ Admin JS. + wp_enqueue_script( 'wz-admin-js' ); + wp_enqueue_script( 'wz-codemirror-js' ); + wp_enqueue_script( 'wz-taxonomy-suggest-js' ); + wp_enqueue_script( 'wz-media-selector-js' ); + } + + /** + * Function to save the metabox. + * + * @param int|string $post_id Post ID. + */ + public function save( $post_id ) { + + $post_meta = array(); + + // Bail if we're doing an auto save. + if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { + return; + } + + // If our nonce isn't there, or we can't verify it, bail. + if ( ! isset( $_POST[ $this->prefix . '_meta_box_nonce' ] ) || ! wp_verify_nonce( sanitize_key( $_POST[ $this->prefix . '_meta_box_nonce' ] ), $this->prefix . '_meta_box' ) ) { + return; + } + + // If our current user can't edit this post, bail. + if ( ! current_user_can( 'edit_post', $post_id ) ) { + return; + } + + if ( empty( $_POST[ $this->settings_key ] ) ) { + return; + } + + $settings_sanitize = new Settings_Sanitize(); + + $posted = $_POST[ $this->settings_key ]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash + + foreach ( $this->registered_settings as $setting ) { + $id = $setting['id']; + $type = isset( $setting['type'] ) ? $setting['type'] : 'text'; + + /** + * Skip settings that are not really settings. + * + * @param array $non_setting_types Array of types which are not settings. + */ + $non_setting_types = apply_filters( $this->prefix . '_metabox_non_setting_types', array( 'header', 'descriptive_text' ) ); + + if ( in_array( $type, $non_setting_types, true ) ) { + continue; + } + + if ( isset( $posted[ $id ] ) ) { + $value = $posted[ $id ]; + $sanitize_callback = is_callable( array( $settings_sanitize, "sanitize_{$type}_field" ) ) ? array( $settings_sanitize, "sanitize_{$type}_field" ) : array( $settings_sanitize, 'sanitize_missing' ); + $post_meta[ $id ] = call_user_func( $sanitize_callback, $value ); + } + } + + // Run the array through a generic function that allows access to all of the settings. + $post_meta = call_user_func( array( $this, 'sanitize_post_meta' ), $post_meta ); + + /** + * Filter the post meta array which contains post-specific settings. + * + * @param array $post_meta Array of ATA metabox settings. + * @param int $post_id Post ID + */ + $post_meta = apply_filters( "{$this->prefix}_meta_key", $post_meta, $post_id ); + + // Now loop through the settings array and either save or delete the meta key. + foreach ( $this->registered_settings as $setting ) { + if ( empty( $post_meta[ $setting['id'] ] ) ) { + delete_post_meta( $post_id, "_{$this->prefix}_{$setting['id']}" ); + } + } + + foreach ( $post_meta as $setting => $value ) { + if ( empty( $post_meta[ $setting ] ) ) { + delete_post_meta( $post_id, "_{$this->prefix}_$setting" ); + } else { + update_post_meta( $post_id, "_{$this->prefix}_$setting", $value ); + } + } + } + + /** + * Function to display the metabox. + * + * @param \WP_Post $post Post object. + */ + public function html( $post ) { + // Add an nonce field so we can check for it later. + wp_nonce_field( $this->prefix . '_meta_box', $this->prefix . '_meta_box_nonce' ); + + $settings_form = new Settings_Form( + array( + 'settings_key' => $this->settings_key, + 'prefix' => $this->prefix, + 'checkbox_modified_text' => $this->checkbox_modified_text, + ) + ); + + echo ''; + foreach ( $this->registered_settings as $setting ) { + + $args = wp_parse_args( + $setting, + array( + 'id' => null, + 'name' => '', + 'desc' => '', + 'type' => null, + 'default' => '', + 'options' => '', + 'max' => null, + 'min' => null, + 'step' => null, + 'size' => null, + 'field_class' => '', + 'field_attributes' => '', + 'placeholder' => '', + ) + ); + + $id = $args['id']; + $value = get_post_meta( $post->ID, "_{$this->prefix}_{$id}", true ); + $args['value'] = ! empty( $value ) ? $value : ( isset( $args['default'] ) ? $args['default'] : $args['options'] ); + $type = isset( $args['type'] ) ? $args['type'] : 'text'; + $callback = method_exists( $settings_form, "callback_{$type}" ) ? array( $settings_form, "callback_{$type}" ) : array( $settings_form, 'callback_missing' ); + + echo ''; + echo ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo ''; + echo ''; + } + echo '
' . $args['name'] . ''; + call_user_func( $callback, $args ); + echo '
'; + + /** + * Action triggered when displaying Better Search meta box. + * + * @param object $post Post object. + */ + do_action( $this->prefix . '_meta_box', $post ); + } + + /** + * Sanitize Post Meta array. + * + * @param array $settings Post meta settings array. + * @return array Sanitized value. + */ + public function sanitize_post_meta( $settings ) { + + // This array holds a list of keys that will be passed through our category/tags loop to determine the ids. + $keys = array( + 'include_on_category' => array( + 'tax' => 'category', + 'ids_field' => 'include_on_category_ids', + ), + 'include_on_post_tag' => array( + 'tax' => 'post_tag', + 'ids_field' => 'include_on_post_tag_ids', + ), + ); + + foreach ( $keys as $key => $fields ) { + if ( isset( $settings[ $key ] ) ) { + $ids = array(); + $names = array(); + + $taxes = array_unique( str_getcsv( $settings[ $key ] ) ); + + foreach ( $taxes as $tax ) { + $tax_name = get_term_by( 'name', $tax, $fields['tax'] ); + + if ( isset( $tax_name->term_taxonomy_id ) ) { + $ids[] = $tax_name->term_taxonomy_id; + $names[] = $tax_name->name; + } + } + $settings[ $fields['ids_field'] ] = join( ',', $ids ); + $settings[ $key ] = Settings_Sanitize::str_putcsv( $names ); + } else { + $settings[ $fields['ids_field'] ] = ''; + } + } + + return $settings; + } +} diff --git a/includes/admin/settings/class-settings-api.php b/includes/admin/settings/class-settings-api.php index 54b3aa2..0402f7c 100644 --- a/includes/admin/settings/class-settings-api.php +++ b/includes/admin/settings/class-settings-api.php @@ -5,1529 +5,860 @@ * Functions to register, read, write and update settings. * Portions of this code have been inspired by Easy Digital Downloads, WordPress Settings Sandbox, WordPress Settings API class, etc. * - * @link https://webberzone.com - * - * @package Knowledgebase - * @subpackage Admin + * @package WebberZone\Knowledge_Base */ -namespace Knowledgebase_Admin; +namespace WebberZone\Knowledge_Base\Admin\Settings; // If this file is called directly, abort. if ( ! defined( 'WPINC' ) ) { die; } -if ( ! class_exists( 'Settings_API' ) ) : +/** + * Settings API wrapper class + * + * @version 2.5.2 + */ +#[\AllowDynamicProperties] +class Settings_API { + /** - * Settings API wrapper class + * Current version number * - * @version 2.2.0 + * @var string */ - class Settings_API { - - /** - * Current version number - * - * @var string - */ - const VERSION = '2.2.0'; - - /** - * Settings Key. - * - * @var string Settings Key. - */ - public $settings_key; - - /** - * Prefix which is used for creating the unique filters and actions. - * - * @var string Prefix. - */ - public $prefix; - - /** - * Translation strings. - * - * @var array Translation strings. - */ - public $translation_strings; - - /** - * Menu type. - * - * @see add_custom_menu_page() - * - * @var string Menu slug. - */ - public $menu_type; - - /** - * The slug name of the parent of the menu. - * - * @var string Menu slug of the parent. - */ - public $parent_slug; - - /** - * The slug name to refer to this menu by (should be unique for this menu). - * - * @var string Menu slug. - */ - public $menu_slug; - - /** - * Default navigation tab. - * - * @var string Default navigation tab. - */ - protected $default_tab; - - /** - * Settings page. - * - * @var string Settings page. - */ - public $settings_page; - - /** - * Admin Footer Text. Displayed at the bottom of the plugin settings page. - * - * @var string Admin Footer Text. - */ - protected $admin_footer_text; - - /** - * Array containing the settings' sections. - * - * @var array Settings sections array. - */ - protected $settings_sections = array(); - - /** - * Array containing the settings' fields. - * - * @var array Settings fields array. - */ - protected $registered_settings = array(); - - /** - * Array containing the settings' fields that need to be upgraded to the current Settings API. - * - * @var array Settings fields array. - */ - protected $upgraded_settings = array(); - - /** - * Help sidebar content. - * - * @var string Admin Footer Text. - */ - protected $help_sidebar; - - /** - * Array of help tabs. - * - * @var array Settings sections array. - */ - protected $help_tabs = array(); - - /** - * Main constructor class. - * - * @param string $settings_key Settings key. - * @param string $prefix Prefix. Used for actions and filters. - * @param mixed $args { - * Array or string of arguments. Default is blank array. - * @type array $translation_strings Translation strings. - * @type array $settings_sections Settings sections. - * @type array $props Properties. - * @type array $registered_settings Registered settings. - * @type array $upgraded_settings Upgraded settings. - * } - */ - public function __construct( $settings_key, $prefix, $args ) { - - if ( ! defined( 'WZ_SETTINGS_API_VERSION' ) ) { - define( 'WZ_SETTINGS_API_VERSION', self::VERSION ); - } - - $this->settings_key = $settings_key; - $this->prefix = $prefix; - - $defaults = array( - 'translation_strings' => array(), - 'props' => array(), - 'settings_sections' => array(), - 'registered_settings' => array(), - 'upgraded_settings' => array(), - ); - $args = wp_parse_args( $args, $defaults ); - - $this->hooks(); - $this->set_translation_strings( $args['translation_strings'] ); - $this->set_props( $args['props'] ); - $this->set_sections( $args['settings_sections'] ); - $this->set_registered_settings( $args['registered_settings'] ); - $this->set_upgraded_settings( $args['upgraded_settings'] ); - } - - /** - * Adds the functions to the appropriate WordPress hooks. - */ - public function hooks() { - add_action( 'admin_menu', array( $this, 'admin_menu' ), 11 ); - add_action( 'admin_init', array( $this, 'admin_init' ) ); - add_filter( 'admin_footer_text', array( $this, 'admin_footer_text' ) ); - add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) ); - } - - /** - * Sets properties. - * - * @param array|string $args { - * Array or string of arguments. Default is blank array. - * - * @type string $menu_type Admin menu type. See add_custom_menu_page() for options. - * @type string $menu_parent Parent menu slug. - * @type string $menu_slug Admin menu slug. - * @type string $default_tab Default tab. - * @type string $admin_footer_text Admin footer text. - * @type string $help_sidebar Help sidebar. - * @type array $help_tabs Help tabs. - * } - */ - public function set_props( $args ) { - - $defaults = array( - 'menu_type' => 'options', - 'parent_slug' => 'options-general.php', - 'menu_slug' => '', - 'default_tab' => 'general', - 'admin_footer_text' => '', - 'help_sidebar' => '', - 'help_tabs' => array(), - ); - - $args = wp_parse_args( $args, $defaults ); - - foreach ( $args as $name => $value ) { - $this->$name = $value; - } - } - - /** - * Sets translation strings. - * - * @param array $strings { - * Array of translation strings. - * - * @type string $page_title Page title. - * @type string $menu_title Menu title. - * @type string $page_header Page header. - * @type string $reset_message Reset message. - * @type string $success_message Success message. - * @type string $save_changes Save changes button label. - * @type string $reset_settings Reset settings button label. - * @type string $reset_button_confirm Reset button confirmation message. - * @type string $checkbox_modified Checkbox modified label. - * } - * - * @return void - */ - public function set_translation_strings( $strings ) { - - // Args prefixed with an underscore are reserved for internal use. - $defaults = array( - 'page_title' => '', - 'menu_title' => '', - 'page_header' => '', - 'reset_message' => __( 'Settings have been reset to their default values. Reload this page to view the updated settings.' ), - 'success_message' => __( 'Settings updated.' ), - 'save_changes' => __( 'Save Changes' ), - 'reset_settings' => __( 'Reset all settings' ), - 'reset_button_confirm' => __( 'Do you really want to reset all these settings to their default values?' ), - 'checkbox_modified' => __( 'Modified from default setting' ), - ); - - $strings = wp_parse_args( $strings, $defaults ); - - $this->translation_strings = $strings; - } - - /** - * Set settings sections - * - * @param array $sections Setting sections array. - * @return object Class object. - */ - public function set_sections( $sections ) { - $this->settings_sections = (array) $sections; - - return $this; - } - - /** - * Add a single section - * - * @param array $section New Section. - * @return object Object of the class instance. - */ - public function add_section( $section ) { - $this->settings_sections[] = $section; - - return $this; - } - - /** - * Set the settings fields for registered settings. - * - * @param array $registered_settings Registered settings array. - * @return object Object of the class instance. - */ - public function set_registered_settings( $registered_settings ) { - $this->registered_settings = (array) $registered_settings; - - return $this; - } - - /** - * Set the settings fields for settings to upgrade. - * - * @param array $upgraded_settings Settings array. - * @return object Object of the class instance. - */ - public function set_upgraded_settings( $upgraded_settings = array() ) { - $this->upgraded_settings = (array) $upgraded_settings; - - return $this; - } - - /** - * Add a menu page to the WordPress admin area. - * - * @param array $menu Array of settings for the menu page. - */ - public function add_custom_menu_page( $menu ) { - $defaults = array( - - // Modes: submenu, management, options, theme, plugins, users, dashboard, posts, media, links, pages, comments. - 'type' => 'submenu', - - // Submenu default settings. - 'parent_slug' => 'options-general.php', - 'page_title' => '', - 'menu_title' => '', - 'capability' => 'manage_options', - 'menu_slug' => '', - 'function' => array( $this, 'plugin_settings' ), - - // Menu default settings. - 'icon_url' => 'dashicons-admin-generic', - 'position' => null, - - ); - $menu = wp_parse_args( $menu, $defaults ); - - switch ( $menu['type'] ) { - case 'submenu': - $menu_page = add_submenu_page( - $menu['parent_slug'], - $menu['page_title'], - $menu['menu_title'], - $menu['capability'], - $menu['menu_slug'], - $menu['function'], - $menu['position'] - ); - break; - case 'management': - case 'options': - case 'theme': - case 'plugins': - case 'users': - case 'dashboard': - case 'posts': - case 'media': - case 'links': - case 'pages': - case 'comments': - $f = 'add_' . $menu['type'] . '_page'; - if ( function_exists( $f ) ) { - $menu_page = $f( - $menu['page_title'], - $menu['menu_title'], - $menu['capability'], - $menu['menu_slug'], - $menu['function'], - $menu['position'] - ); - } - break; - default: - $menu_page = add_menu_page( - $menu['page_title'], - $menu['menu_title'], - $menu['capability'], - $menu['menu_slug'], - $menu['function'], - $menu['icon_url'], - $menu['position'] - ); - break; - } - - return $menu_page; - } - - - /** - * Add admin menu. - */ - public function admin_menu() { - $menu = array( - 'type' => $this->menu_type, - 'parent_slug' => $this->parent_slug, - 'page_title' => $this->translation_strings['page_title'], - 'menu_title' => $this->translation_strings['menu_title'], - 'capability' => 'manage_options', - 'menu_slug' => $this->menu_slug, - 'function' => array( $this, 'plugin_settings' ), - ); - - $this->settings_page = $this->add_custom_menu_page( $menu ); - - // Load the settings contextual help. - add_action( 'load-' . $this->settings_page, array( $this, 'settings_help' ) ); - } - - /** - * Enqueue scripts and styles. - * - * @param string $hook The current admin page. - */ - public function admin_enqueue_scripts( $hook ) { - - if ( $hook === $this->settings_page ) { - $this->enqueue_scripts_styles(); - } - } - - /** - * Enqueues all scripts, styles, settings, and templates necessary to use the Settings API. - */ - public function enqueue_scripts_styles() { - - $minimize = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; - - wp_enqueue_style( 'wp-color-picker' ); - - wp_enqueue_media(); - wp_enqueue_script( 'wp-color-picker' ); - wp_enqueue_script( 'jquery' ); - wp_enqueue_script( 'jquery-ui-autocomplete' ); - wp_enqueue_script( 'jquery-ui-tabs' ); - - wp_enqueue_code_editor( - array( - 'type' => 'text/html', - 'codemirror' => array( - 'indentUnit' => 2, - 'tabSize' => 2, - ), - ) - ); - - wp_enqueue_script( - 'wz-admin-js', - plugins_url( 'js/admin-scripts' . $minimize . '.js', __FILE__ ), - array( 'jquery' ), - self::VERSION, - true - ); - wp_enqueue_script( - 'wz-codemirror-js', - plugins_url( 'js/apply-codemirror' . $minimize . '.js', __FILE__ ), - array( 'jquery' ), - self::VERSION, - true - ); - wp_enqueue_script( - 'wz-taxonomy-suggest-js', - plugins_url( 'js/taxonomy-suggest' . $minimize . '.js', __FILE__ ), - array( 'jquery' ), - self::VERSION, - true - ); - } - - /** - * Initialize and registers the settings sections and fileds to WordPress - * - * Usually this should be called at `admin_init` hook. - * - * This public function gets the initiated settings sections and fields. Then - * registers them to WordPress and ready for use. - */ - public function admin_init() { - - $settings_key = $this->settings_key; - - if ( false === get_option( $settings_key ) ) { - add_option( $settings_key, $this->settings_defaults() ); - } - - foreach ( $this->registered_settings as $section => $settings ) { - - add_settings_section( - "{$settings_key}_{$section}", // ID used to identify this section and with which to register options. - __return_null(), // No title, we will handle this via a separate function. - '__return_false', // No callback function needed. We'll process this separately. - "{$settings_key}_{$section}" // Page on which these options will be added. - ); - - foreach ( $settings as $setting ) { - - $args = wp_parse_args( - $setting, - array( - 'section' => $section, - 'id' => null, - 'name' => '', - 'desc' => '', - 'type' => null, - 'options' => '', - 'max' => null, - 'min' => null, - 'step' => null, - 'size' => null, - 'field_class' => '', - 'field_attributes' => '', - 'placeholder' => '', - ) - ); - - $id = $args['id']; - $name = $args['name']; - $type = isset( $args['type'] ) ? $args['type'] : 'text'; - $callback = method_exists( $this, "callback_{$type}" ) ? array( $this, "callback_{$type}" ) : array( $this, 'callback_missing' ); - - add_settings_field( - "{$settings_key}[{$id}]", // ID of the settings field. We save it within the settings array. - $name, // Label of the setting. - $callback, // Function to handle the setting. - "{$settings_key}_{$section}", // Page to display the setting. In our case it is the section as defined above. - "{$settings_key}_{$section}", // Name of the section. - $args - ); - } - } - - // Register the settings into the options table. - register_setting( $settings_key, $settings_key, array( $this, 'settings_sanitize' ) ); - } - - /** - * Flattens $this->registered_settings into $setting[id] => $setting[type] format. - * - * @return array Default settings - */ - public function get_registered_settings_types() { - - $options = array(); - - // Populate some default values. - foreach ( $this->registered_settings as $tab => $settings ) { - foreach ( $settings as $option ) { - $options[ $option['id'] ] = $option['type']; - } - } - - /** - * Filters the settings array. - * - * @param array $options Default settings. - */ - return apply_filters( $this->prefix . '_get_settings_types', $options ); - } - - - /** - * Default settings. - * - * @return array Default settings - */ - public function settings_defaults() { - - $options = array(); - - // Populate some default values. - foreach ( $this->registered_settings as $tab => $settings ) { - foreach ( $settings as $option ) { - // When checkbox is set to true, set this to 1. - if ( 'checkbox' === $option['type'] && ! empty( $option['options'] ) ) { - $options[ $option['id'] ] = 1; - } else { - $options[ $option['id'] ] = 0; - } - // If an option is set. - if ( in_array( $option['type'], array( 'textarea', 'css', 'html', 'text', 'url', 'csv', 'color', 'numbercsv', 'postids', 'posttypes', 'number', 'wysiwyg', 'file', 'password' ), true ) && isset( $option['options'] ) ) { - $options[ $option['id'] ] = $option['options']; - } - if ( in_array( $option['type'], array( 'multicheck', 'radio', 'select', 'radiodesc', 'thumbsizes' ), true ) && isset( $option['default'] ) ) { - $options[ $option['id'] ] = $option['default']; - } - } - } + const VERSION = '2.5.2'; - $upgraded_settings = $this->upgraded_settings; - - if ( false !== $upgraded_settings ) { - $options = array_merge( $options, $upgraded_settings ); - } - - /** - * Filters the default settings array. - * - * @param array $options Default settings. - */ - return apply_filters( $this->prefix . '_settings_defaults', $options ); - } - - - /** - * Get the default option for a specific key - * - * @param string $key Key of the option to fetch. - * @return mixed - */ - public function get_default_option( $key = '' ) { - - $default_settings = $this->settings_defaults(); - - if ( array_key_exists( $key, $default_settings ) ) { - return $default_settings[ $key ]; - } else { - return false; - } - } - - - /** - * Reset settings. - * - * @return void - */ - public function settings_reset() { - delete_option( $this->settings_key ); - } - - /** - * Get field description for display. - * - * @param array $args settings Arguments array. - */ - public function get_field_description( $args ) { - if ( ! empty( $args['desc'] ) ) { - $desc = '

' . wp_kses_post( $args['desc'] ) . '

'; - } else { - $desc = ''; - } - - /** - * After Settings Output filter - * - * @param string $desc Description of the field. - * @param array $args Arguments array. - */ - $desc = apply_filters( $this->prefix . '_setting_field_description', $desc, $args ); - return $desc; - } - - /** - * Get the value of a settings field. - * - * @param string $option Settings field name. - * @param string $default_value Default text if it's not found. - * @return string - */ - public function get_option( $option, $default_value = '' ) { - - $options = get_option( $this->settings_key ); - - if ( isset( $options[ $option ] ) ) { - return $options[ $option ]; - } - - return $default_value; - } - - /** - * Miscellaneous callback funcion - * - * @param array $args Arguments array. - * @return void - */ - public function callback_missing( $args ) { - /* translators: 1: Code. */ - printf( esc_html__( 'The callback function used for the %1$s setting is missing.' ), '' . esc_attr( $args['id'] ) . '' ); - } - - /** - * Header Callback - * - * Renders the header. - * - * @param array $args Arguments passed by the setting. - * @return void - */ - public function callback_header( $args ) { - $html = $this->get_field_description( $args ); - - /** - * After Settings Output filter - * - * @param string $html HTML string. - * @param array $args Arguments array. - */ - echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } - - /** - * Descriptive text callback. - * - * Renders descriptive text onto the settings field. - * - * @param array $args Array of arguments. - * @return void - */ - public function callback_descriptive_text( $args ) { - $this->callback_header( $args ); - } - - /** - * Display text fields. - * - * @param array $args Array of arguments. - */ - public function callback_text( $args ) { - - $value = $this->get_option( $args['id'], $args['options'] ); - $size = sanitize_html_class( ( isset( $args['size'] ) && ! is_null( $args['size'] ) ) ? $args['size'] : 'regular' ); - $class = sanitize_html_class( $args['field_class'] ); - $placeholder = empty( $args['placeholder'] ) ? '' : ' placeholder="' . $args['placeholder'] . '"'; - $disabled = ! empty( $args['disabled'] ) ? ' disabled="disabled"' : ''; - $readonly = ( isset( $args['readonly'] ) && true === $args['readonly'] ) ? ' readonly="readonly"' : ''; - $attributes = $disabled . $readonly; - - foreach ( (array) $args['field_attributes'] as $attribute => $val ) { - $attributes .= sprintf( ' %1$s="%2$s"', $attribute, esc_attr( $val ) ); - } - - $html = sprintf( - '', - $this->settings_key, - sanitize_key( $args['id'] ), - $class . ' ' . $size . '-text', - esc_attr( stripslashes( $value ) ), - $attributes, - $placeholder - ); - $html .= $this->get_field_description( $args ); - - /** This filter has been defined in class-settings-api.php */ - echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } - - /** - * Display url fields. - * - * @param array $args Array of arguments. - */ - public function callback_url( $args ) { - $this->callback_text( $args ); - } - - /** - * Display csv fields. - * - * @param array $args Array of arguments. - */ - public function callback_csv( $args ) { - $this->callback_text( $args ); - } - - /** - * Display color fields. - * - * @param array $args Array of arguments. - */ - public function callback_color( $args ) { - $this->callback_text( $args ); - } - - /** - * Display numbercsv fields. - * - * @param array $args Array of arguments. - */ - public function callback_numbercsv( $args ) { - $this->callback_text( $args ); - } - - /** - * Display postids fields. - * - * @param array $args Array of arguments. - */ - public function callback_postids( $args ) { - $this->callback_text( $args ); - } - - /** - * Display textarea. - * - * @param array $args Array of arguments. - * @return void - */ - public function callback_textarea( $args ) { - - $value = $this->get_option( $args['id'], $args['options'] ); - $class = sanitize_html_class( $args['field_class'] ); - - $html = sprintf( - '', - $this->settings_key, - sanitize_key( $args['id'] ), - esc_textarea( stripslashes( $value ) ), - 'large-text ' . $class - ); - $html .= $this->get_field_description( $args ); - - /** This filter has been defined in class-settings-api.php */ - echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } - - /** - * Display CSS fields. - * - * @param array $args Array of arguments. - * @return void - */ - public function callback_css( $args ) { - $this->callback_textarea( $args ); - } - - /** - * Display HTML fields. - * - * @param array $args Array of arguments. - * @return void - */ - public function callback_html( $args ) { - $this->callback_textarea( $args ); - } - - /** - * Display checkboxes. - * - * @param array $args Array of arguments. - * @return void - */ - public function callback_checkbox( $args ) { - - $value = $this->get_option( $args['id'], $args['options'] ); - $checked = ! empty( $value ) ? checked( 1, $value, false ) : ''; - $default = isset( $args['options'] ) ? (int) $args['options'] : ''; + /** + * Settings Key. + * + * @var string Settings Key. + */ + public $settings_key; - $html = sprintf( '', $this->settings_key, sanitize_key( $args['id'] ) ); - $html .= sprintf( '', $this->settings_key, sanitize_key( $args['id'] ), $checked ); - $html .= ( (bool) $value !== (bool) $default ) ? '' . $this->translation_strings['checkbox_modified'] . '' : ''; // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison - $html .= $this->get_field_description( $args ); + /** + * Prefix which is used for creating the unique filters and actions. + * + * @var string Prefix. + */ + public $prefix; - /** This filter has been defined in class-settings-api.php */ - echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } + /** + * Translation strings. + * + * @see set_translation_strings() + * + * @var array Translation strings. + */ + public $translation_strings; - /** - * Multicheck Callback - * - * Renders multiple checkboxes. - * - * @param array $args Array of arguments. - * @return void - */ - public function callback_multicheck( $args ) { - $html = ''; - - $value = $this->get_option( $args['id'], $args['options'] ); - - if ( ! empty( $args['options'] ) ) { - $html .= sprintf( '', $this->settings_key, $args['id'] ); - - foreach ( $args['options'] as $key => $option ) { - if ( isset( $value[ $key ] ) ) { - $enabled = $key; - } else { - $enabled = null; - } - - $html .= sprintf( - ' ', - $this->settings_key, - sanitize_key( $args['id'] ), - sanitize_key( $key ), - esc_attr( $key ), - checked( $key, $enabled, false ) - ); - $html .= sprintf( - '
', - $this->settings_key, - sanitize_key( $args['id'] ), - sanitize_key( $key ), - $option - ); - } + /** + * Menus. + * + * @var array Menus. + */ + public $menus = array(); - $html .= $this->get_field_description( $args ); - } + /** + * Menu pages. + * + * @var array Menu pages. + */ + public $menu_pages = array(); - /** This filter has been defined in class-settings-api.php */ - echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } + /** + * Default navigation tab. + * + * @var string Default navigation tab. + */ + protected $default_tab; - /** - * Radio Callback - * - * Renders radio boxes. - * - * @param array $args Array of arguments. - * @return void - */ - public function callback_radio( $args ) { - $html = ''; - - $value = $this->get_option( $args['id'], $args['default'] ); - - foreach ( $args['options'] as $key => $option ) { - $html .= sprintf( - ' ', - $this->settings_key, - sanitize_key( $args['id'] ), - $key, - checked( $value, $key, false ) - ); - $html .= sprintf( - '
', - $this->settings_key, - sanitize_key( $args['id'] ), - $key, - $option - ); - } + /** + * Settings page. + * + * @var string Settings page. + */ + public $settings_page = ''; - $html .= $this->get_field_description( $args ); + /** + * Admin Footer Text. Displayed at the bottom of the plugin settings page. + * + * @var string Admin Footer Text. + */ + protected $admin_footer_text; - /** This filter has been defined in class-settings-api.php */ - echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } + /** + * Array containing the settings' sections. + * + * @var array Settings sections array. + */ + protected $settings_sections = array(); - /** - * Radio callback with description. - * - * Renders radio boxes with each item having it separate description. - * - * @param array $args Array of arguments. - * @return void - */ - public function callback_radiodesc( $args ) { - $html = ''; - - $value = $this->get_option( $args['id'], $args['default'] ); - - foreach ( $args['options'] as $option ) { - $html .= sprintf( - ' ', - $this->settings_key, - sanitize_key( $args['id'] ), - $option['id'], - checked( $value, $option['id'], false ) - ); - $html .= sprintf( - '', - $this->settings_key, - sanitize_key( $args['id'] ), - $option['id'], - $option['name'] - ); + /** + * Array containing the settings' fields. + * + * @var array Settings fields array. + */ + protected $registered_settings = array(); - $html .= ': ' . wp_kses_post( $option['description'] ) . '
'; - } + /** + * Array containing the settings' fields that need to be upgraded to the current Settings API. + * + * @var array Settings fields array. + */ + protected $upgraded_settings = array(); - $html .= $this->get_field_description( $args ); + /** + * Help sidebar content. + * + * @var string Admin Footer Text. + */ + protected $help_sidebar; - /** This filter has been defined in class-settings-api.php */ - echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } + /** + * Array of help tabs. + * + * @var array Settings sections array. + */ + protected $help_tabs = array(); - /** - * Radio callback with description. - * - * Renders radio boxes with each item having it separate description. - * - * @param array $args Array of arguments. - * @return void - */ - public function callback_thumbsizes( $args ) { - $html = ''; - - $value = $this->get_option( $args['id'], $args['default'] ); - - foreach ( $args['options'] as $name => $option ) { - $html .= sprintf( - ' ', - $this->settings_key, - sanitize_key( $args['id'] ), - $name, - checked( $value, $name, false ) - ); - $html .= sprintf( - '
', - $this->settings_key, - sanitize_key( $args['id'] ), - $name, - (int) $option['width'], - (int) $option['height'], - (bool) $option['crop'] ? ' ' . __( 'cropped' ) : '' - ); - } + /** + * Settings form. + * + * @since 2.0.0 + * + * @var object Settings form. + */ + public $settings_form; - $html .= $this->get_field_description( $args ); + /** + * Main constructor class. + * + * @param string $settings_key Settings key. + * @param string $prefix Prefix. Used for actions and filters. + * @param mixed $args { + * Array or string of arguments. Default is blank array. + * @type array $translation_strings Translation strings. + * @type array $settings_sections Settings sections. + * @type array $props Properties. + * @type array $registered_settings Registered settings. + * @type array $upgraded_settings Upgraded settings. + * } + */ + public function __construct( $settings_key, $prefix, $args ) { - /** This filter has been defined in class-settings-api.php */ - echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + if ( ! defined( 'WZ_SETTINGS_API_VERSION' ) ) { + define( 'WZ_SETTINGS_API_VERSION', self::VERSION ); } - /** - * Number Callback - * - * Renders number fields. - * - * @param array $args Array of arguments. - * @return void - */ - public function callback_number( $args ) { - $value = $this->get_option( $args['id'], $args['options'] ); - $max = isset( $args['max'] ) ? intval( $args['max'] ) : 999999; - $min = isset( $args['min'] ) ? intval( $args['min'] ) : 0; - $step = isset( $args['step'] ) ? intval( $args['step'] ) : 1; - $size = ( isset( $args['size'] ) && ! is_null( $args['size'] ) ) ? $args['size'] : 'regular'; - $placeholder = empty( $args['placeholder'] ) ? '' : ' placeholder="' . esc_attr( $args['placeholder'] ) . '"'; - - $html = sprintf( - '', - esc_attr( (string) $step ), - esc_attr( (string) $max ), - esc_attr( (string) $min ), - sanitize_html_class( $size ) . '-text', - sanitize_key( $args['id'] ), - esc_attr( stripslashes( $value ) ), - $placeholder, - $this->settings_key - ); - $html .= $this->get_field_description( $args ); + $this->settings_key = $settings_key; + $this->prefix = $prefix; + + $defaults = array( + 'translation_strings' => array(), + 'props' => array(), + 'settings_sections' => array(), + 'registered_settings' => array(), + 'upgraded_settings' => array(), + ); + $args = wp_parse_args( $args, $defaults ); + + $this->hooks(); + $this->set_translation_strings( $args['translation_strings'] ); + $this->set_props( $args['props'] ); + $this->set_sections( $args['settings_sections'] ); + $this->set_registered_settings( $args['registered_settings'] ); + $this->set_upgraded_settings( $args['upgraded_settings'] ); + } - /** This filter has been defined in class-settings-api.php */ - echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } + /** + * Adds the functions to the appropriate WordPress hooks. + */ + public function hooks() { + add_action( 'admin_menu', array( $this, 'admin_menu' ), 11 ); + add_action( 'admin_init', array( $this, 'admin_init' ) ); + add_filter( 'admin_footer_text', array( $this, 'admin_footer_text' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) ); + add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) ); + } - /** - * Select Callback - * - * Renders select fields. - * - * @param array $args Array of arguments. - * @return void - */ - public function callback_select( $args ) { - $value = $this->get_option( $args['id'], $args['options'] ); + /** + * Filters the CSS classes for the body tag in the admin. + * + * @param string $classes Space-separated list of CSS classes. + * @return string Space-separated list of CSS classes. + */ + public function admin_body_class( $classes ) { + $current_screen = get_current_screen(); - if ( isset( $args['chosen'] ) ) { - $chosen = 'class="chosen"'; - } else { - $chosen = ''; - } + if ( in_array( $current_screen->id, $this->menu_pages, true ) ) { + $classes .= ' ' . $this->prefix . '-dashboard-page'; + } + return $classes; + } - $html = sprintf( ''; - $html .= $this->get_field_description( $args ); + $args = wp_parse_args( $args, $defaults ); - /** This filter has been defined in class-settings-api.php */ - echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + foreach ( $args as $name => $value ) { + $this->$name = $value; } + } - /** - * Display posttypes fields. - * - * @param array $args Array of arguments. - * @return void - */ - public function callback_posttypes( $args ) { - $html = ''; + /** + * Sets translation strings. + * + * @param array $strings { + * Array of translation strings. + * + * @type string $page_title Page title. + * @type string $menu_title Menu title. + * @type string $page_header Page header. + * @type string $reset_message Reset message. + * @type string $success_message Success message. + * @type string $save_changes Save changes button label. + * @type string $reset_settings Reset settings button label. + * @type string $reset_button_confirm Reset button confirmation message. + * @type string $checkbox_modified Checkbox modified label. + * } + * + * @return void + */ + public function set_translation_strings( $strings ) { + + // Args prefixed with an underscore are reserved for internal use. + $defaults = array( + 'page_header' => '', + 'reset_message' => __( 'Settings have been reset to their default values. Reload this page to view the updated settings.' ), + 'success_message' => __( 'Settings updated.' ), + 'save_changes' => __( 'Save Changes' ), + 'reset_settings' => __( 'Reset all settings' ), + 'reset_button_confirm' => __( 'Do you really want to reset all these settings to their default values?' ), + 'checkbox_modified' => __( 'Modified from default setting' ), + ); + + $strings = wp_parse_args( $strings, $defaults ); + + $this->translation_strings = $strings; + } - $options = $this->get_option( $args['id'], $args['options'] ); + /** + * Set settings sections + * + * @param array $sections Setting sections array in the format of: id => Title. + * @return object Class object. + */ + public function set_sections( $sections ) { + $this->settings_sections = (array) $sections; - // If post_types contains a query string then parse it with wp_parse_args. - if ( is_string( $options ) && strpos( $options, '=' ) ) { - $post_types = wp_parse_args( $options ); - } else { - $post_types = wp_parse_list( $options ); - } + return $this; + } - $wp_post_types = get_post_types( - array( - 'public' => true, - ) - ); - $posts_types_inc = array_intersect( $wp_post_types, $post_types ); + /** + * Add a single section + * + * @param array $section New Section. + * @return object Object of the class instance. + */ + public function add_section( $section ) { + $this->settings_sections[] = $section; - foreach ( $wp_post_types as $wp_post_type ) { + return $this; + } - $html .= sprintf( - ' ', - sanitize_key( $args['id'] ), - esc_attr( $wp_post_type ), - checked( true, in_array( $wp_post_type, $posts_types_inc, true ), false ), - $this->settings_key - ); - $html .= sprintf( '
', sanitize_key( $args['id'] ), $wp_post_type, $this->settings_key ); + /** + * Set the settings fields for registered settings. + * + * @param array $registered_settings { + * Array of settings in format id => attributes. + * @type string $section Section title. + * @type string $id Field ID. + * @type string $name Field name. + * @type string $desc Field description. + * @type string $type Field type. + * @type string $options Field default option(s). + * @type string $max Field max. Applicable for numbers. + * @type string $min Field min. Applicable for numbers. + * @type string $step Field step. Applicable for numbers. + * @type string $size Field size. Applicable for text and textarea. + * @type string $field_class CSS class. + * @type array $field_attributes HTML Attributes in the form of attribute => value. + * @type string $placeholder Placeholder. Applicable for text and textarea. + * @type string $sanitize_callback Sanitize callback. + * } + * } + * } + * @return object Object of the class instance. + */ + public function set_registered_settings( $registered_settings ) { + $this->registered_settings = (array) $registered_settings; - } + return $this; + } + + /** + * Set the settings fields for settings to upgrade. + * + * @param array $upgraded_settings Settings array. + * @return object Object of the class instance. + */ + public function set_upgraded_settings( $upgraded_settings = array() ) { + $this->upgraded_settings = (array) $upgraded_settings; - $html .= $this->get_field_description( $args ); + return $this; + } - /** This filter has been defined in class-settings-api.php */ - echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + /** + * Add a menu page to the WordPress admin area. + * + * @param array $menu Array of settings for the menu page. + */ + public function add_custom_menu_page( $menu ) { + $defaults = array( + + // Modes: submenu, management, options, theme, plugins, users, dashboard, posts, media, links, pages, comments. + 'type' => 'submenu', + + // Submenu default settings. + 'parent_slug' => 'options-general.php', + 'page_title' => '', + 'menu_title' => '', + 'capability' => $this->get_capability_for_menu(), + 'menu_slug' => '', + 'function' => array( $this, 'plugin_settings' ), + + // Menu default settings. + 'icon_url' => 'dashicons-admin-generic', + 'position' => null, + + ); + $menu = wp_parse_args( $menu, $defaults ); + + switch ( $menu['type'] ) { + case 'submenu': + $menu_page = add_submenu_page( + $menu['parent_slug'], + $menu['page_title'], + $menu['menu_title'], + $menu['capability'], + $menu['menu_slug'], + $menu['function'], + $menu['position'] + ); + break; + case 'management': + case 'options': + case 'theme': + case 'plugins': + case 'users': + case 'dashboard': + case 'posts': + case 'media': + case 'links': + case 'pages': + case 'comments': + $f = 'add_' . $menu['type'] . '_page'; + + $menu_page = null; + if ( function_exists( $f ) ) { + $menu_page = $f( + $menu['page_title'], + $menu['menu_title'], + $menu['capability'], + $menu['menu_slug'], + $menu['function'], + $menu['position'] + ); + } + break; + default: + $menu_page = add_menu_page( + $menu['page_title'], + $menu['menu_title'], + $menu['capability'], + $menu['menu_slug'], + $menu['function'], + $menu['icon_url'], + $menu['position'] + ); + break; } + return $menu_page; + } + - /** - * Display taxonomies fields. - * - * @param array $args Array of arguments. - * @return void - */ - public function callback_taxonomies( $args ) { - $html = ''; + /** + * Add admin menu. + */ + public function admin_menu() { + global ${$this->prefix . '_menu_pages'}; - $options = $this->get_option( $args['id'], $args['options'] ); + foreach ( $this->menus as $menu ) { + $menu_page = $this->add_custom_menu_page( $menu ); - // If taxonomies contains a query string then parse it with wp_parse_args. - if ( is_string( $options ) && strpos( $options, '=' ) ) { - $taxonomies = wp_parse_args( $options ); - } else { - $taxonomies = wp_parse_list( $options ); + $this->menu_pages[ $menu['menu_slug'] ] = $menu_page; + if ( isset( $menu['settings_page'] ) && $menu['settings_page'] ) { + $this->settings_page = $menu_page; } + } + ${$this->prefix . '_menu_pages'} = $this->menu_pages; - /* Fetch taxonomies */ - $argsc = array( - 'public' => true, - ); - $output = 'objects'; - $operator = 'and'; - $wp_taxonomies = get_taxonomies( $argsc, $output, $operator ); + // Load the settings contextual help. + add_action( 'load-' . $this->settings_page, array( $this, 'settings_help' ) ); + } - $taxonomies_inc = array_intersect( wp_list_pluck( (array) $wp_taxonomies, 'name' ), $taxonomies ); + /** + * Get the appropriate capability for the menu based on the user's roles and settings. + * + * @param array $roles Array of roles to check. + * @param string $base_capability The default capability. + * @param \WP_User $current_user The current user object. + * @param array $role_capabilities Array of role capabilities. + * @return string The capability to use for the menu. + */ + public static function get_capability_for_menu( $roles = array(), $base_capability = 'manage_options', $current_user = null, $role_capabilities = array() ) { + if ( ! $current_user ) { + $current_user = wp_get_current_user(); + } - foreach ( $wp_taxonomies as $wp_taxonomy ) { + if ( empty( $roles ) || in_array( 'administrator', $current_user->roles, true ) ) { + return $base_capability; + } - $html .= sprintf( - ' ', - sanitize_key( $args['id'] ), - esc_attr( $wp_taxonomy->name ), - checked( true, in_array( $wp_taxonomy->name, $taxonomies_inc, true ), false ), - $this->settings_key - ); - $html .= sprintf( - '
', - sanitize_key( $args['id'] ), - esc_attr( $wp_taxonomy->name ), - $wp_taxonomy->labels->name, - $this->settings_key - ); + if ( empty( $role_capabilities ) ) { + $role_capabilities = array( + 'editor' => 'edit_others_posts', + 'author' => 'publish_posts', + 'contributor' => 'edit_posts', + 'subscriber' => 'read', + ); + } + foreach ( $current_user->roles as $role ) { + if ( in_array( $role, $roles, true ) && isset( $role_capabilities[ $role ] ) ) { + return $role_capabilities[ $role ]; } + } - $html .= $this->get_field_description( $args ); + return $base_capability; + } - /** This filter has been defined in class-settings-api.php */ - echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + /** + * Enqueue scripts and styles. + * + * @param string $hook The current admin page. + */ + public function admin_enqueue_scripts( $hook ) { + + $minimize = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; + + // Settings API scripts. + wp_register_script( + 'wz-admin-js', + plugins_url( 'js/settings-admin-scripts' . $minimize . '.js', __FILE__ ), + array( 'jquery' ), + self::VERSION, + true + ); + wp_register_script( + 'wz-codemirror-js', + plugins_url( 'js/apply-codemirror' . $minimize . '.js', __FILE__ ), + array( 'jquery' ), + self::VERSION, + true + ); + wp_register_script( + 'wz-taxonomy-suggest-js', + plugins_url( 'js/taxonomy-suggest' . $minimize . '.js', __FILE__ ), + array( 'jquery' ), + self::VERSION, + true + ); + wp_register_script( + 'wz-media-selector-js', + plugins_url( 'js/media-selector' . $minimize . '.js', __FILE__ ), + array( 'jquery' ), + self::VERSION, + true + ); + + if ( $hook === $this->settings_page ) { + self::enqueue_scripts_styles(); } + } + /** + * Enqueues all scripts, styles, settings, and templates necessary to use the Settings API. + */ + public static function enqueue_scripts_styles() { + + wp_enqueue_style( 'wp-color-picker' ); + + wp_enqueue_media(); + wp_enqueue_script( 'wp-color-picker' ); + wp_enqueue_script( 'jquery' ); + wp_enqueue_script( 'jquery-ui-autocomplete' ); + wp_enqueue_script( 'jquery-ui-tabs' ); + + wp_enqueue_code_editor( + array( + 'type' => 'text/html', + 'codemirror' => array( + 'indentUnit' => 2, + 'tabSize' => 2, + ), + ) + ); - /** - * Displays a rich text textarea for a settings field. - * - * @param array $args Array of arguments. - */ - public function callback_wysiwyg( $args ) { + wp_enqueue_script( 'wz-admin-js' ); + wp_enqueue_script( 'wz-codemirror-js' ); + wp_enqueue_script( 'wz-taxonomy-suggest-js' ); + } + + /** + * Initialize and registers the settings sections and fields to WordPress + * + * Usually this should be called at `admin_init` hook. + * + * This public function gets the initiated settings sections and fields. Then + * registers them to WordPress and ready for use. + */ + public function admin_init() { - $value = $this->get_option( $args['id'], $args['options'] ); - $size = isset( $args['size'] ) && ! is_null( $args['size'] ) ? $args['size'] : '500px'; + $settings_key = $this->settings_key; - echo '
'; + if ( false === get_option( $settings_key ) ) { + add_option( $settings_key, $this->settings_defaults() ); + } - $editor_settings = array( - 'teeny' => true, - 'textarea_name' => $args['section'] . '[' . $args['id'] . ']', - 'textarea_rows' => 10, + $this->settings_form = new Settings_Form( + array( + 'settings_key' => $settings_key, + 'prefix' => $this->prefix, + 'checkbox_modified_text' => $this->translation_strings['checkbox_modified'], + ) + ); + + foreach ( $this->registered_settings as $section => $settings ) { + + add_settings_section( + "{$settings_key}_{$section}", // ID used to identify this section and with which to register options. + '', // No title, we will handle this via a separate function. + '__return_false', // No callback function needed. We'll process this separately. + "{$settings_key}_{$section}" // Page on which these options will be added. ); - if ( isset( $args['options'] ) && is_array( $args['options'] ) ) { - $editor_settings = array_merge( $editor_settings, $args['options'] ); + foreach ( $settings as $setting ) { + + $args = wp_parse_args( + $setting, + array( + 'section' => $section, + 'id' => null, + 'name' => '', + 'desc' => '', + 'type' => null, + 'default' => '', + 'options' => '', + 'max' => null, + 'min' => null, + 'step' => null, + 'size' => null, + 'field_class' => '', + 'field_attributes' => '', + 'placeholder' => '', + 'pro' => false, + ) + ); + + $id = $args['id']; + $name = $args['name']; + $type = isset( $args['type'] ) ? $args['type'] : 'text'; + $callback = method_exists( $this->settings_form, "callback_{$type}" ) ? array( $this->settings_form, "callback_{$type}" ) : array( $this->settings_form, 'callback_missing' ); + + add_settings_field( + "{$settings_key}[{$id}]", // ID of the settings field. We save it within the settings array. + $name, // Label of the setting. + $callback, // Function to handle the setting. + "{$settings_key}_{$section}", // Page to display the setting. In our case it is the section as defined above. + "{$settings_key}_{$section}", // Name of the section. + $args + ); } + } + + // Register the settings into the options table. + register_setting( + $settings_key, + $settings_key, + array( + 'sanitize_callback' => array( $this, 'settings_sanitize' ), + 'show_in_rest' => true, + ) + ); + } - wp_editor( $value, $args['section'] . '-' . $args['id'], $editor_settings ); + /** + * Flattens $this->registered_settings into $setting[id] => $setting[type] format. + * + * @return array Default settings + */ + public function get_registered_settings_types() { - echo '
'; + $options = array(); - echo $this->get_field_description( $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + // Populate some default values. + foreach ( $this->registered_settings as $tab => $settings ) { + foreach ( $settings as $option ) { + $options[ $option['id'] ] = $option['type']; + } } /** - * Displays a file upload field for a settings field. + * Filters the settings array. * - * @param array $args Array of arguments. + * @param array $options Default settings. */ - public function callback_file( $args ) { - - $value = $this->get_option( $args['id'], $args['options'] ); - $size = sanitize_html_class( ( isset( $args['size'] ) && ! is_null( $args['size'] ) ) ? $args['size'] : 'regular' ); - $class = sanitize_html_class( $args['field_class'] ); - $label = isset( $args['options']['button_label'] ) ? $args['options']['button_label'] : __( 'Choose File' ); - - $html = sprintf( - '', - $class . ' ' . $size . '-text file-url', - $this->settings_key, - sanitize_key( $args['id'] ), - esc_attr( $value ) - ); - $html .= ''; - $html .= $this->get_field_description( $args ); + return apply_filters( $this->prefix . '_get_settings_types', $options ); + } + - /** This filter has been defined in class-settings-api.php */ - echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + /** + * Default settings. + * + * @return array Default settings + */ + public function settings_defaults() { + + $options = array(); + + // Populate some default values. + foreach ( $this->registered_settings as $tab => $settings ) { + foreach ( $settings as $option ) { + // When checkbox is set to true, set this to 1. + if ( 'checkbox' === $option['type'] && ! empty( $option['options'] ) ) { + $options[ $option['id'] ] = 1; + } else { + $options[ $option['id'] ] = 0; + } + // If an option is set. + if ( in_array( $option['type'], array( 'textarea', 'css', 'html', 'text', 'url', 'csv', 'color', 'numbercsv', 'postids', 'posttypes', 'number', 'wysiwyg', 'file', 'password' ), true ) && isset( $option['options'] ) ) { + $options[ $option['id'] ] = $option['options']; + } + if ( in_array( $option['type'], array( 'multicheck', 'radio', 'select', 'radiodesc', 'thumbsizes' ), true ) && isset( $option['default'] ) ) { + $options[ $option['id'] ] = $option['default']; + } + } } - /** - * Displays a password field for a settings field. - * - * @param array $args Array of arguments. - */ - public function callback_password( $args ) { - - $value = $this->get_option( $args['id'], $args['options'] ); - $size = sanitize_html_class( ( isset( $args['size'] ) && ! is_null( $args['size'] ) ) ? $args['size'] : 'regular' ); - $class = sanitize_html_class( $args['field_class'] ); - - $html = sprintf( - '', - $class . ' ' . $size . '-text', - $this->settings_key, - sanitize_key( $args['id'] ), - esc_attr( $value ) - ); - $html .= $this->get_field_description( $args ); + $upgraded_settings = $this->upgraded_settings; - /** This filter has been defined in class-settings-api.php */ - echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + if ( false !== $upgraded_settings ) { + $options = array_merge( $options, $upgraded_settings ); } /** - * Sanitize the form data being submitted. + * Filters the default settings array. * - * @param array $input Input unclean array. - * @return array Sanitized array + * @param array $options Default settings. */ - public function settings_sanitize( $input ) { + return apply_filters( $this->prefix . '_settings_defaults', $options ); + } - // This should be set if a form is submitted, so let's save it in the $referrer variable. - if ( empty( $_POST['_wp_http_referer'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing - return $input; - } - parse_str( sanitize_text_field( wp_unslash( $_POST['_wp_http_referer'] ) ), $referrer ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + /** + * Get the default option for a specific key + * + * @param string $key Key of the option to fetch. + * @return mixed + */ + public function get_default_option( $key = '' ) { - // Check if we need to set to defaults. - $reset = isset( $_POST['settings_reset'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + $default_settings = $this->settings_defaults(); - if ( $reset ) { - $this->settings_reset(); - $settings = get_option( $this->settings_key ); + if ( array_key_exists( $key, $default_settings ) ) { + return $default_settings[ $key ]; + } else { + return false; + } + } - add_settings_error( $this->prefix . '-notices', '', $this->translation_strings['reset_message'], 'error' ); - return $settings; - } + /** + * Reset settings. + * + * @return void + */ + public function settings_reset() { + delete_option( $this->settings_key ); + } - // Get the various settings we've registered. - $settings = get_option( $this->settings_key ); - $settings_types = $this->get_registered_settings_types(); + /** + * Sanitize the form data being submitted. + * + * @param array $input Input unclean array. + * @return array Sanitized array + */ + public function settings_sanitize( $input ) { - // Get the tab. This is also our settings' section. - $tab = isset( $referrer['tab'] ) ? $referrer['tab'] : $this->default_tab; + // This should be set if a form is submitted, so let's save it in the $referrer variable. + if ( empty( $_POST['_wp_http_referer'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + return $input; + } - $input = $input ? $input : array(); + parse_str( sanitize_text_field( wp_unslash( $_POST['_wp_http_referer'] ) ), $referrer ); // phpcs:ignore WordPress.Security.NonceVerification.Missing - /** - * Filter the settings for the tab. e.g. prefix_settings_general_sanitize. - * - * @param array $input Input unclean array - */ - $input = apply_filters( $this->prefix . '_settings_' . $tab . '_sanitize', $input ); + // Check if we need to set to defaults. + $reset = isset( $_POST['settings_reset'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing - // Create an output array by merging the existing settings with the ones submitted. - $output = array_merge( $settings, $input ); + if ( $reset ) { + $this->settings_reset(); + $settings = get_option( $this->settings_key ); - // Loop through each setting being saved and pass it through a sanitization filter. - foreach ( $settings_types as $key => $type ) { + add_settings_error( $this->prefix . '-notices', '', $this->translation_strings['reset_message'], 'error' ); - /** - * Skip settings that are not really settings. - * - * @param array $non_setting_types Array of types which are not settings. - */ - $non_setting_types = apply_filters( $this->prefix . '_non_setting_types', array( 'header', 'descriptive_text' ) ); + return $settings; + } - if ( in_array( $type, $non_setting_types, true ) ) { - continue; - } + // Get the various settings we've registered. + $settings = get_option( $this->settings_key ); + $settings_types = $this->get_registered_settings_types(); - if ( array_key_exists( $key, $output ) ) { + // Get the tab. This is also our settings' section. + $tab = isset( $referrer['tab'] ) ? $referrer['tab'] : $this->default_tab; - $sanitize_callback = $this->get_sanitize_callback( $key ); + $input = $input ? $input : array(); - // If callback is set, call it. - if ( $sanitize_callback ) { - $output[ $key ] = call_user_func( $sanitize_callback, $output[ $key ] ); - continue; - } - } + /** + * Filter the settings for the tab. e.g. prefix_settings_general_sanitize. + * + * @param array $input Input unclean array + */ + $input = apply_filters( $this->prefix . '_settings_' . $tab . '_sanitize', $input ); - // Delete any key that is not present when we submit the input array. - if ( ! isset( $input[ $key ] ) ) { - unset( $output[ $key ] ); - } + // Create an output array by merging the existing settings with the ones submitted. + $output = array_merge( $settings, $input ); - // Delete any settings that are no longer part of our registered settings. - if ( array_key_exists( $key, $output ) && ! array_key_exists( $key, $settings_types ) ) { - unset( $output[ $key ] ); - } - } - add_settings_error( $this->prefix . '-notices', '', $this->translation_strings['success_message'], 'updated' ); + // Loop through each setting being saved and pass it through a sanitization filter. + foreach ( $settings_types as $key => $type ) { /** - * Filter the settings array before it is returned. + * Skip settings that are not really settings. * - * @param array $output Settings array. - * @param array $input Input settings array. + * @param array $non_setting_types Array of types which are not settings. */ - return apply_filters( $this->prefix . '_settings_sanitize', $output, $input ); - } + $non_setting_types = apply_filters( $this->prefix . '_non_setting_types', array( 'header', 'descriptive_text' ) ); - /** - * Get sanitization callback for given Settings key. - * - * @param string $key Settings key. - * - * @return string|bool Callback function or false if callback isn't found. - */ - public function get_sanitize_callback( $key = '' ) { - if ( empty( $key ) ) { - return false; + if ( in_array( $type, $non_setting_types, true ) ) { + continue; } - // Iterate over registered fields and see if we can find proper callback. - foreach ( $this->registered_settings as $section => $settings ) { - foreach ( $settings as $option ) { - if ( $option['id'] !== $key ) { - continue; - } + if ( array_key_exists( $key, $output ) ) { - // Return the callback name. - $sanitize_callback = false; + $sanitize_callback = $this->get_sanitize_callback( $key ); - if ( isset( $option['sanitize_callback'] ) && is_callable( $option['sanitize_callback'] ) ) { - $sanitize_callback = $option['sanitize_callback']; - return $sanitize_callback; - } - - if ( is_callable( array( $this, 'sanitize_' . $option['type'] . '_field' ) ) ) { - $sanitize_callback = array( $this, 'sanitize_' . $option['type'] . '_field' ); - return $sanitize_callback; - } - - return $sanitize_callback; + // If callback is set, call it. + if ( $sanitize_callback ) { + $output[ $key ] = call_user_func( $sanitize_callback, $output[ $key ] ); + continue; } } - return false; - } + // Delete any key that is not present when we submit the input array. + if ( ! isset( $input[ $key ] ) ) { + unset( $output[ $key ] ); + } - /** - * Sanitize text fields - * - * @param string $value The field value. - * @return string Sanitizied value - */ - public function sanitize_text_field( $value ) { - return $this->sanitize_textarea_field( $value ); + // Delete any settings that are no longer part of our registered settings. + if ( array_key_exists( $key, $output ) && ! array_key_exists( $key, $settings_types ) ) { + unset( $output[ $key ] ); + } } - /** - * Sanitize number fields - * - * @param string $value The field value. - * @return string $value Sanitized value - */ - public function sanitize_number_field( $value ) { - return filter_var( $value, FILTER_SANITIZE_NUMBER_INT ); - } + add_settings_error( $this->prefix . '-notices', '', $this->translation_strings['success_message'], 'updated' ); /** - * Sanitize CSV fields + * Filter the settings array before it is returned. * - * @param string $value The field value. - * @return string Sanitizied value + * @param array $output Settings array. + * @param array $input Input settings array. */ - public function sanitize_csv_field( $value ) { - return implode( ',', array_map( 'trim', explode( ',', sanitize_text_field( wp_unslash( $value ) ) ) ) ); - } + return apply_filters( $this->prefix . '_settings_sanitize', $output, $input ); + } - /** - * Sanitize CSV fields which hold numbers - * - * @param string $value The field value. - * @return string Sanitized value - */ - public function sanitize_numbercsv_field( $value ) { - return implode( ',', array_filter( array_map( 'absint', explode( ',', sanitize_text_field( wp_unslash( $value ) ) ) ) ) ); + /** + * Get sanitization callback for given Settings key. + * + * @param string $key Settings key. + * + * @return mixed Callback function or false if callback isn't found. + */ + public function get_sanitize_callback( $key = '' ) { + if ( empty( $key ) ) { + return false; } - /** - * Sanitize CSV fields which hold post IDs - * - * @param string $value The field value. - * @return string Sanitized value - */ - public function sanitize_postids_field( $value ) { - $ids = array_filter( array_map( 'absint', explode( ',', sanitize_text_field( wp_unslash( $value ) ) ) ) ); + $settings_sanitize = new Settings_Sanitize(); - foreach ( $ids as $key => $value ) { - if ( false === get_post_status( $value ) ) { - unset( $ids[ $key ] ); + // Iterate over registered fields and see if we can find proper callback. + foreach ( $this->registered_settings as $section => $settings ) { + foreach ( $settings as $setting ) { + if ( $setting['id'] !== $key ) { + continue; } - } - - return implode( ',', $ids ); - } - - /** - * Sanitize textarea fields - * - * @param string $value The field value. - * @return string Sanitized value - */ - public function sanitize_textarea_field( $value ) { - - global $allowedposttags; - - // We need more tags to allow for script and style. - $moretags = array( - 'script' => array( - 'type' => true, - 'src' => true, - 'async' => true, - 'defer' => true, - 'charset' => true, - ), - 'style' => array( - 'type' => true, - 'media' => true, - 'scoped' => true, - ), - 'link' => array( - 'rel' => true, - 'type' => true, - 'href' => true, - 'media' => true, - 'sizes' => true, - 'hreflang' => true, - ), - ); - - $allowedtags = array_merge( $allowedposttags, $moretags ); - - /** - * Filter allowed tags allowed when sanitizing text and textarea fields. - * - * @param array $allowedtags Allowed tags array. - */ - $allowedtags = apply_filters( $this->prefix . '_sanitize_allowed_tags', $allowedtags ); - - return wp_kses( wp_unslash( $value ), $allowedtags ); - } - /** - * Sanitize checkbox fields - * - * @param mixed $value The field value. - * @return int Sanitized value - */ - public function sanitize_checkbox_field( $value ) { - $value = ( -1 === (int) $value ) ? 0 : 1; + // Return the callback name. + $sanitize_callback = false; - return $value; - } + if ( isset( $setting['sanitize_callback'] ) && is_callable( $setting['sanitize_callback'] ) ) { + $sanitize_callback = $setting['sanitize_callback']; + return $sanitize_callback; + } - /** - * Sanitize post_types fields - * - * @param array $value The field value. - * @return string $value Sanitized value - */ - public function sanitize_posttypes_field( $value ) { - $post_types = is_array( $value ) ? array_map( 'sanitize_text_field', wp_unslash( $value ) ) : array( 'post', 'page' ); + if ( is_callable( array( $settings_sanitize, 'sanitize_' . $setting['type'] . '_field' ) ) ) { + $sanitize_callback = array( $settings_sanitize, 'sanitize_' . $setting['type'] . '_field' ); + return $sanitize_callback; + } - return implode( ',', $post_types ); + return $sanitize_callback; + } } - /** - * Sanitize post_types fields - * - * @param array $value The field value. - * @return string $value Sanitized value - */ - public function sanitize_taxonomies_field( $value ) { - $taxonomies = is_array( $value ) ? array_map( 'sanitize_text_field', wp_unslash( $value ) ) : array(); - - return implode( ',', $taxonomies ); - } + return false; + } - /** - * Render the settings page. - */ - public function plugin_settings() { - ob_start(); - ?> + /** + * Render the settings page. + */ + public function plugin_settings() { + ob_start(); + ?>
+ prefix . '_settings_page_header_before' ); ?>

translation_strings['page_header'] ); ?>

prefix . '_settings_page_header' ); ?> @@ -1535,15 +866,15 @@ public function plugin_settings() {
- show_navigation(); ?> - show_form(); ?> + show_navigation(); ?> + show_form(); ?>
- +
@@ -1555,54 +886,54 @@ public function plugin_settings() { settings_sections ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'general'; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended + /** + * Show navigations as tab + * + * Shows all the settings section labels as tab + */ + public function show_navigation() { + $active_tab = isset( $_GET['tab'] ) && array_key_exists( sanitize_key( wp_unslash( $_GET['tab'] ) ), $this->settings_sections ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'general'; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended - $html = '
'; - echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } + echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } - /** - * Show the section settings forms - * - * This public function displays every sections in a different form - */ - public function show_form() { - ob_start(); - ?> + /** + * Show the section settings forms + * + * This public function displays every sections in a different form + */ + public function show_form() { + ob_start(); + ?>
- settings_key ); ?> + settings_key ); ?> - settings_sections as $tab_id => $tab_name ) : ?> + settings_sections as $tab_id => $tab_name ) : ?>
@@ -1654,42 +985,40 @@ public function show_form() { admin_footer_text ) && get_current_screen()->id === $this->settings_page ) { + if ( ! empty( $this->admin_footer_text ) && get_current_screen()->id === $this->settings_page ) { - $text = $this->admin_footer_text; + $text = $this->admin_footer_text; - return str_replace( '', '', $footer_text ) . ' | ' . $text . ''; - } else { - return $footer_text; - } + return str_replace( '', '', $footer_text ) . ' | ' . $text . ''; + } else { + return $footer_text; } + } - /** - * Function to add the contextual help in the settings page. - */ - public function settings_help() { - $screen = get_current_screen(); + /** + * Function to add the contextual help in the settings page. + */ + public function settings_help() { + $screen = get_current_screen(); - if ( $screen->id !== $this->settings_page ) { - return; - } + if ( $screen->id !== $this->settings_page ) { + return; + } - $screen->set_help_sidebar( $this->help_sidebar ); + $screen->set_help_sidebar( $this->help_sidebar ); - foreach ( $this->help_tabs as $tab ) { - $screen->add_help_tab( $tab ); - } + foreach ( $this->help_tabs as $tab ) { + $screen->add_help_tab( $tab ); } } - -endif; +} diff --git a/includes/admin/settings/class-settings-form.php b/includes/admin/settings/class-settings-form.php new file mode 100644 index 0000000..35ddff1 --- /dev/null +++ b/includes/admin/settings/class-settings-form.php @@ -0,0 +1,746 @@ + '', + 'prefix' => '', + 'checkbox_modified_text' => '', + ); + $args = wp_parse_args( $args, $defaults ); + + foreach ( $args as $name => $value ) { + $this->$name = $value; + } + } + + /** + * Get field description for display. + * + * @param array $args settings Arguments array. + * + * @return string Description of the field. + */ + public function get_field_description( $args ) { + if ( ! empty( $args['desc'] ) ) { + $desc = '

' . wp_kses_post( $args['desc'] ) . '

'; + } else { + $desc = ''; + } + + /** + * After Settings Output filter + * + * @param string $desc Description of the field. + * @param array $args Arguments array. + */ + $desc = apply_filters( $this->prefix . '_setting_field_description', $desc, $args ); + return $desc; + } + + /** + * Get the value of a settings field. + * + * @param string $option Settings field name. + * @param string $default_value Default text if it's not found. + * @return string + */ + public function get_option( $option, $default_value = '' ) { + + $options = \get_option( $this->settings_key ); + + if ( isset( $options[ $option ] ) ) { + return $options[ $option ]; + } + + return $default_value; + } + + /** + * Miscellaneous callback funcion + * + * @param array $args Arguments array. + * @return void + */ + public function callback_missing( $args ) { + /* translators: 1: Code. */ + printf( esc_html__( 'The callback function used for the %1$s setting is missing.' ), '' . esc_attr( $args['id'] ) . '' ); + } + + /** + * Header Callback + * + * Renders the header. + * + * @param array $args Arguments passed by the setting. + * @return void + */ + public function callback_header( $args ) { + $html = $this->get_field_description( $args ); + + /** + * After Settings Output filter + * + * @param string $html HTML string. + * @param array $args Arguments array. + */ + echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + /** + * Descriptive text callback. + * + * Renders descriptive text onto the settings field. + * + * @param array $args Array of arguments. + * @return void + */ + public function callback_descriptive_text( $args ) { + $this->callback_header( $args ); + } + + /** + * Display text fields. + * + * @param array $args Array of arguments. + */ + public function callback_text( $args ) { + + $value = isset( $args['value'] ) ? $args['value'] : $this->get_option( $args['id'], $args['options'] ); + $size = sanitize_html_class( isset( $args['size'] ) ? $args['size'] : 'regular' ); + $class = sanitize_html_class( $args['field_class'] ); + $placeholder = empty( $args['placeholder'] ) ? '' : ' placeholder="' . $args['placeholder'] . '"'; + $disabled = ( ! empty( $args['disabled'] ) || $args['pro'] ) ? ' disabled="disabled"' : ''; + $readonly = ( isset( $args['readonly'] ) && true === $args['readonly'] ) ? ' readonly="readonly"' : ''; + $attributes = $disabled . $readonly; + + foreach ( (array) $args['field_attributes'] as $attribute => $val ) { + $attributes .= sprintf( ' %1$s="%2$s"', $attribute, esc_attr( $val ) ); + } + + $html = sprintf( + '', + $this->settings_key, + sanitize_key( $args['id'] ), + $class . ' ' . $size . '-text', + esc_attr( stripslashes( $value ) ), + $attributes, + $placeholder + ); + $html .= $this->get_field_description( $args ); + + /** This filter has been defined in class-settings-api.php */ + echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + /** + * Display url fields. + * + * @param array $args Array of arguments. + */ + public function callback_url( $args ) { + $this->callback_text( $args ); + } + + /** + * Display csv fields. + * + * @param array $args Array of arguments. + */ + public function callback_csv( $args ) { + $this->callback_text( $args ); + } + + /** + * Display color fields. + * + * @param array $args Array of arguments. + */ + public function callback_color( $args ) { + $this->callback_text( $args ); + } + + /** + * Display numbercsv fields. + * + * @param array $args Array of arguments. + */ + public function callback_numbercsv( $args ) { + $this->callback_text( $args ); + } + + /** + * Display postids fields. + * + * @param array $args Array of arguments. + */ + public function callback_postids( $args ) { + $this->callback_text( $args ); + } + + /** + * Display textarea. + * + * @param array $args Array of arguments. + * @return void + */ + public function callback_textarea( $args ) { + + $value = isset( $args['value'] ) ? $args['value'] : $this->get_option( $args['id'], $args['options'] ); + $class = sanitize_html_class( $args['field_class'] ); + $placeholder = empty( $args['placeholder'] ) ? '' : ' placeholder="' . $args['placeholder'] . '"'; + $disabled = ( ! empty( $args['disabled'] ) || $args['pro'] ) ? ' disabled="disabled"' : ''; + $readonly = ( isset( $args['readonly'] ) && true === $args['readonly'] ) ? ' readonly="readonly"' : ''; + $attributes = $disabled . $readonly; + + $html = sprintf( + '', + $this->settings_key, + sanitize_key( $args['id'] ), + esc_textarea( stripslashes( $value ) ), + 'large-text ' . $class, + $attributes, + $placeholder + ); + $html .= $this->get_field_description( $args ); + + /** This filter has been defined in class-settings-api.php */ + echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + /** + * Display CSS fields. + * + * @param array $args Array of arguments. + * @return void + */ + public function callback_css( $args ) { + $this->callback_textarea( $args ); + } + + /** + * Display HTML fields. + * + * @param array $args Array of arguments. + * @return void + */ + public function callback_html( $args ) { + $this->callback_textarea( $args ); + } + + /** + * Display checkboxes. + * + * @param array $args Array of arguments. + * @return void + */ + public function callback_checkbox( $args ) { + + $value = isset( $args['value'] ) ? $args['value'] : $this->get_option( $args['id'], $args['options'] ); + $checked = ! empty( $value ) ? checked( 1, $value, false ) : ''; + $default = isset( $args['options'] ) ? (int) $args['options'] : ''; + $disabled = ( ! empty( $args['disabled'] ) || $args['pro'] ) ? ' disabled="disabled"' : ''; + + $html = sprintf( + '', + $this->settings_key, + sanitize_key( $args['id'] ) + ); + $html .= sprintf( + '', + $this->settings_key, + sanitize_key( $args['id'] ), + $checked, + $disabled + ); + $html .= ( (bool) $value !== (bool) $default ) ? '' . $this->checkbox_modified_text . '' : ''; + $html .= $this->get_field_description( $args ); + + /** This filter has been defined in class-settings-api.php */ + echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + /** + * Multicheck Callback + * + * Renders multiple checkboxes. + * + * @param array $args Array of arguments. + * @return void + */ + public function callback_multicheck( $args ) { + $html = ''; + + $value = isset( $args['value'] ) ? $args['value'] : $this->get_option( $args['id'], $args['default'] ); + $value_array = wp_parse_list( $value ); + $disabled = ( ! empty( $args['disabled'] ) || $args['pro'] ) ? ' disabled="disabled"' : ''; + + if ( ! empty( $args['options'] ) ) { + $html .= sprintf( '', $this->settings_key, sanitize_key( $args['id'] ) ); + + foreach ( $args['options'] as $key => $option ) { + if ( in_array( $key, $value_array, true ) ) { + $enabled = $key; + } else { + $enabled = null; + } + + $html .= sprintf( + ' ', + $this->settings_key, + sanitize_key( $args['id'] ), + sanitize_key( $key ), + esc_attr( $key ), + checked( $key, $enabled, false ), + $disabled + ); + $html .= sprintf( + '
', + $this->settings_key, + sanitize_key( $args['id'] ), + sanitize_key( $key ), + $option + ); + } + } + $html .= $this->get_field_description( $args ); + + /** This filter has been defined in class-settings-api.php */ + echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + /** + * Radio Callback + * + * Renders radio boxes. + * + * @param array $args Array of arguments. + * @return void + */ + public function callback_radio( $args ) { + $html = ''; + + $value = isset( $args['value'] ) ? $args['value'] : $this->get_option( $args['id'], $args['default'] ); + $disabled = ( ! empty( $args['disabled'] ) || $args['pro'] ) ? ' disabled="disabled"' : ''; + + foreach ( $args['options'] as $key => $option ) { + $html .= sprintf( + ' ', + $this->settings_key, + sanitize_key( $args['id'] ), + $key, + checked( $value, $key, false ), + $disabled + ); + $html .= sprintf( + '
', + $this->settings_key, + sanitize_key( $args['id'] ), + $key, + $option + ); + } + + $html .= $this->get_field_description( $args ); + + /** This filter has been defined in class-settings-api.php */ + echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + /** + * Radio callback with description. + * + * Renders radio boxes with each item having it separate description. + * + * @param array $args Array of arguments. + * @return void + */ + public function callback_radiodesc( $args ) { + $html = ''; + + $value = isset( $args['value'] ) ? $args['value'] : $this->get_option( $args['id'], $args['default'] ); + $disabled = ( ! empty( $args['disabled'] ) || $args['pro'] ) ? ' disabled="disabled"' : ''; + + foreach ( $args['options'] as $option ) { + $html .= sprintf( + ' ', + $this->settings_key, + sanitize_key( $args['id'] ), + $option['id'], + checked( $value, $option['id'], false ), + $disabled + ); + $html .= sprintf( + '', + $this->settings_key, + sanitize_key( $args['id'] ), + $option['id'], + $option['name'], + wp_kses_post( $option['description'] ) + ); + + $html .= '
'; + } + + $html .= $this->get_field_description( $args ); + + /** This filter has been defined in class-settings-api.php */ + echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + /** + * Radio callback with description. + * + * Renders radio boxes with each item having it separate description. + * + * @param array $args Array of arguments. + * @return void + */ + public function callback_thumbsizes( $args ) { + $html = ''; + + $thumb_size = $this->prefix . '_thumbnail'; + + if ( ! isset( $args['options'][ $thumb_size ] ) ) { + $args['options'][ $thumb_size ] = array( + 'name' => $thumb_size, + 'width' => call_user_func_array( $this->prefix . '_get_option', array( 'thumb_width', 150 ) ), + 'height' => call_user_func_array( $this->prefix . '_get_option', array( 'thumb_height', 150 ) ), + 'crop' => call_user_func_array( $this->prefix . '_get_option', array( 'thumb_crop', true ) ), + ); + } + + $value = isset( $args['value'] ) ? $args['value'] : $this->get_option( $args['id'], $args['default'] ); + + foreach ( $args['options'] as $name => $option ) { + $html .= sprintf( + ' ', + $this->settings_key, + sanitize_key( $args['id'] ), + $name, + checked( $value, $name, false ) + ); + $html .= sprintf( + '
', + $this->settings_key, + sanitize_key( $args['id'] ), + $name, + (int) $option['width'], + (int) $option['height'], + (bool) $option['crop'] ? ' ' . __( 'cropped' ) : '' + ); + } + + $html .= $this->get_field_description( $args ); + + /** This filter has been defined in class-settings-api.php */ + echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + /** + * Number Callback + * + * Renders number fields. + * + * @param array $args Array of arguments. + * @return void + */ + public function callback_number( $args ) { + $value = isset( $args['value'] ) ? $args['value'] : $this->get_option( $args['id'], $args['options'] ); + $max = isset( $args['max'] ) ? intval( $args['max'] ) : 999999; + $min = isset( $args['min'] ) ? intval( $args['min'] ) : 0; + $step = isset( $args['step'] ) ? intval( $args['step'] ) : 1; + $size = isset( $args['size'] ) ? $args['size'] : 'regular'; + $placeholder = empty( $args['placeholder'] ) ? '' : ' placeholder="' . esc_attr( $args['placeholder'] ) . '"'; + $disabled = ( ! empty( $args['disabled'] ) || $args['pro'] ) ? ' disabled="disabled"' : ''; + + $html = sprintf( + '', + esc_attr( (string) $step ), + esc_attr( (string) $max ), + esc_attr( (string) $min ), + sanitize_html_class( $size ) . '-text', + sanitize_key( $args['id'] ), + esc_attr( stripslashes( $value ) ), + $placeholder, + $this->settings_key, + $disabled + ); + $html .= $this->get_field_description( $args ); + + /** This filter has been defined in class-settings-api.php */ + echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + /** + * Select Callback + * + * Renders select fields. + * + * @param array $args Array of arguments. + * @return void + */ + public function callback_select( $args ) { + $value = isset( $args['value'] ) ? $args['value'] : $this->get_option( $args['id'], $args['default'] ); + $disabled = ( ! empty( $args['disabled'] ) || $args['pro'] ) ? ' disabled="disabled"' : ''; + + if ( isset( $args['chosen'] ) ) { + $chosen = 'class="chosen"'; + } else { + $chosen = ''; + } + + $html = sprintf( + ''; + $html .= $this->get_field_description( $args ); + + /** This filter has been defined in class-settings-api.php */ + echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + /** + * Display posttypes fields. + * + * @param array $args Array of arguments. + * @return void + */ + public function callback_posttypes( $args ) { + $html = ''; + + $options = isset( $args['value'] ) ? $args['value'] : $this->get_option( $args['id'], $args['options'] ); + $disabled = ( ! empty( $args['disabled'] ) || $args['pro'] ) ? ' disabled="disabled"' : ''; + + // If post_types contains a query string then parse it with wp_parse_args. + if ( is_string( $options ) && strpos( $options, '=' ) ) { + $post_types = wp_parse_args( $options ); + } else { + $post_types = wp_parse_list( $options ); + } + + $wp_post_types = get_post_types( + array( + 'public' => true, + ), + 'objects' + ); + + $posts_types_inc = array_intersect( wp_list_pluck( $wp_post_types, 'name' ), $post_types ); + + $html .= sprintf( + '', + $this->settings_key, + sanitize_key( $args['id'] ) + ); + + foreach ( $wp_post_types as $wp_post_type ) { + + $html .= sprintf( + '
', + sanitize_key( $args['id'] ), + esc_attr( $wp_post_type->name ), + checked( true, in_array( $wp_post_type->name, $posts_types_inc, true ), false ), + $this->settings_key, + $wp_post_type->label, + $disabled + ); + + } + + $html .= $this->get_field_description( $args ); + + /** This filter has been defined in class-settings-api.php */ + echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + + /** + * Display taxonomies fields. + * + * @param array $args Array of arguments. + * @return void + */ + public function callback_taxonomies( $args ) { + $html = ''; + + $options = isset( $args['value'] ) ? $args['value'] : $this->get_option( $args['id'], $args['options'] ); + + // If taxonomies contains a query string then parse it with wp_parse_args. + if ( is_string( $options ) && strpos( $options, '=' ) ) { + $taxonomies = wp_parse_args( $options ); + } else { + $taxonomies = wp_parse_list( $options ); + } + + /* Fetch taxonomies */ + $argsc = array( + 'public' => true, + ); + $output = 'objects'; + $operator = 'and'; + $wp_taxonomies = get_taxonomies( $argsc, $output, $operator ); + + $taxonomies_inc = array_intersect( wp_list_pluck( (array) $wp_taxonomies, 'name' ), $taxonomies ); + + $html .= sprintf( '', $this->settings_key, sanitize_key( $args['id'] ) ); + + foreach ( $wp_taxonomies as $wp_taxonomy ) { + + $html .= sprintf( + '
', + sanitize_key( $args['id'] ), + esc_attr( $wp_taxonomy->name ), + checked( true, in_array( $wp_taxonomy->name, $taxonomies_inc, true ), false ), + $this->settings_key, + $wp_taxonomy->labels->name + ); + + } + + $html .= $this->get_field_description( $args ); + + /** This filter has been defined in class-settings-api.php */ + echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + + /** + * Displays a rich text textarea for a settings field. + * + * @param array $args Array of arguments. + */ + public function callback_wysiwyg( $args ) { + + $value = isset( $args['value'] ) ? $args['value'] : $this->get_option( $args['id'], $args['options'] ); + $size = isset( $args['size'] ) ? $args['size'] : '500px'; + + echo '
'; + + $editor_settings = array( + 'teeny' => true, + 'textarea_name' => $args['section'] . '[' . $args['id'] . ']', + 'textarea_rows' => 10, + ); + + if ( isset( $args['options'] ) && is_array( $args['options'] ) ) { + $editor_settings = array_merge( $editor_settings, $args['options'] ); + } + + wp_editor( $value, $args['section'] . '-' . $args['id'], $editor_settings ); + + echo '
'; + + echo $this->get_field_description( $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + /** + * Displays a file upload field for a settings field. + * + * @param array $args Array of arguments. + */ + public function callback_file( $args ) { + + $value = isset( $args['value'] ) ? $args['value'] : $this->get_option( $args['id'], $args['options'] ); + $size = sanitize_html_class( isset( $args['size'] ) ? $args['size'] : 'regular' ); + $class = sanitize_html_class( $args['field_class'] ); + $label = isset( $args['options']['button_label'] ) ? $args['options']['button_label'] : __( 'Choose File' ); + + $html = sprintf( + '', + $class . ' ' . $size . '-text file-url', + $this->settings_key, + sanitize_key( $args['id'] ), + esc_attr( $value ) + ); + $html .= ''; + $html .= $this->get_field_description( $args ); + + /** This filter has been defined in class-settings-api.php */ + echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + /** + * Displays a password field for a settings field. + * + * @param array $args Array of arguments. + */ + public function callback_password( $args ) { + + $value = isset( $args['value'] ) ? $args['value'] : $this->get_option( $args['id'], $args['options'] ); + $size = sanitize_html_class( isset( $args['size'] ) ? $args['size'] : 'regular' ); + $class = sanitize_html_class( $args['field_class'] ); + + $html = sprintf( + '', + $class . ' ' . $size . '-text', + $this->settings_key, + sanitize_key( $args['id'] ), + esc_attr( $value ) + ); + $html .= $this->get_field_description( $args ); + + /** This filter has been defined in class-settings-api.php */ + echo apply_filters( $this->prefix . '_after_setting_output', $html, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } +} diff --git a/includes/admin/settings/class-settings-sanitize.php b/includes/admin/settings/class-settings-sanitize.php new file mode 100644 index 0000000..93f31c9 --- /dev/null +++ b/includes/admin/settings/class-settings-sanitize.php @@ -0,0 +1,286 @@ +sanitize_textarea_field( $value ); + } + + /** + * Sanitize number fields + * + * @param string $value The field value. + * @return string Sanitized value + */ + public function sanitize_number_field( $value ) { + return filter_var( $value, FILTER_SANITIZE_NUMBER_INT ); + } + + /** + * Sanitize CSV fields + * + * @param string $value The field value. + * @return string Sanitizied value + */ + public function sanitize_csv_field( $value ) { + return implode( ',', array_map( 'trim', explode( ',', sanitize_text_field( wp_unslash( $value ) ) ) ) ); + } + + /** + * Sanitize CSV fields which hold numbers + * + * @param string $value The field value. + * @return string Sanitized value + */ + public function sanitize_numbercsv_field( $value ) { + return implode( ',', array_filter( array_map( 'absint', explode( ',', sanitize_text_field( wp_unslash( $value ) ) ) ) ) ); + } + + /** + * Sanitize CSV fields which hold post IDs + * + * @param string $value The field value. + * @return string Sanitized value + */ + public function sanitize_postids_field( $value ) { + $ids = array_filter( array_map( 'absint', explode( ',', sanitize_text_field( wp_unslash( $value ) ) ) ) ); + + foreach ( $ids as $key => $value ) { + if ( false === get_post_status( $value ) ) { + unset( $ids[ $key ] ); + } + } + + return implode( ',', $ids ); + } + + /** + * Sanitize textarea fields + * + * @param string $value The field value. + * @return string Sanitized value + */ + public function sanitize_textarea_field( $value ) { + + global $allowedposttags; + + // We need more tags to allow for script and style. + $moretags = array( + 'script' => array( + 'type' => true, + 'src' => true, + 'async' => true, + 'defer' => true, + 'charset' => true, + ), + 'style' => array( + 'type' => true, + 'media' => true, + 'scoped' => true, + ), + 'link' => array( + 'rel' => true, + 'type' => true, + 'href' => true, + 'media' => true, + 'sizes' => true, + 'hreflang' => true, + ), + ); + + $allowedtags = array_merge( $allowedposttags, $moretags ); + + /** + * Filter allowed tags allowed when sanitizing text and textarea fields. + * + * @param array $allowedtags Allowed tags array. + */ + $allowedtags = apply_filters( 'wz_sanitize_allowed_tags', $allowedtags ); + + return wp_kses( wp_unslash( $value ), $allowedtags ); + } + + /** + * Sanitize checkbox fields + * + * @param mixed $value The field value. + * @return int Sanitized value + */ + public function sanitize_checkbox_field( $value ) { + $value = ( -1 === (int) $value ) ? 0 : 1; + + return $value; + } + + /** + * Sanitize multicheck fields + * + * @param array|int $value The field value. + * @return string $value Sanitized value + */ + public function sanitize_multicheck_field( $value ) { + $values = ( -1 === (int) $value ) ? array() : array_map( 'sanitize_text_field', (array) wp_unslash( $value ) ); + + return implode( ',', $values ); + } + + /** + * Sanitize post_types fields + * + * @param array|int $value The field value. + * @return string $value Sanitized value + */ + public function sanitize_posttypes_field( $value ) { + return $this->sanitize_multicheck_field( $value ); + } + + /** + * Sanitize post_types fields + * + * @param array|int $value The field value. + * @return string $value Sanitized value + */ + public function sanitize_taxonomies_field( $value ) { + return $this->sanitize_multicheck_field( $value ); + } + + /** + * Sanitize color fields. + * + * @param string $value The field value. + * @return string Sanitized value + */ + public function sanitize_color_field( $value ) { + return sanitize_hex_color( $value ); + } + + /** + * Convert a string to CSV. + * + * @param array $input_array Input string. + * @param string $delimiter Delimiter. + * @param string $enclosure Enclosure. + * @param string $terminator Terminating string. + * @return string CSV string. + */ + public static function str_putcsv( $input_array, $delimiter = ',', $enclosure = '"', $terminator = "\n" ) { + // First convert associative array to numeric indexed array. + $work_array = array(); + foreach ( $input_array as $key => $value ) { + $work_array[] = $value; + } + + $output = ''; + $array_size = count( $work_array ); + + for ( $i = 0; $i < $array_size; $i++ ) { + // Nested array, process nest item. + if ( is_array( $work_array[ $i ] ) ) { + $output .= self::str_putcsv( $work_array[ $i ], $delimiter, $enclosure, $terminator ); + } else { + switch ( gettype( $work_array[ $i ] ) ) { + // Manually set some strings. + case 'NULL': + $sp_format = ''; + break; + case 'boolean': + $sp_format = ( true === $work_array[ $i ] ) ? 'true' : 'false'; + break; + // Make sure sprintf has a good datatype to work with. + case 'integer': + $sp_format = '%i'; + break; + case 'double': + $sp_format = '%0.2f'; + break; + case 'string': + $sp_format = '%s'; + $work_array[ $i ] = str_replace( "$enclosure", "$enclosure$enclosure", $work_array[ $i ] ); + break; + // Unknown or invalid items for a csv - note: the datatype of array is already handled above, assuming the data is nested. + case 'object': + case 'resource': + default: + $sp_format = ''; + break; + } + $output .= sprintf( '%2$s' . $sp_format . '%2$s', $work_array[ $i ], $enclosure ); + $output .= ( $i < ( $array_size - 1 ) ) ? $delimiter : $terminator; + } + } + + return $output; + } + + /** + * Processes category/taxonomy slugs and adds a new element to the settings array containing the term taxonomy IDs. + * + * @param array $settings The settings array containing the taxonomy slugs to sanitize. + * @param string $source_key The key in the settings array containing the slugs. Pattern is Name (taxonomy:term_taxonomy_id). + * @param string $target_key The key in the settings array to store the sanitized term taxonomy IDs. + * @return void + */ + public static function sanitize_tax_slugs( &$settings, $source_key, $target_key ) { + if ( isset( $settings[ $source_key ] ) ) { + $slugs = array_unique( str_getcsv( $settings[ $source_key ] ) ); + + foreach ( $slugs as $slug ) { + // Pattern is Name (taxonomy:term_taxonomy_id). + preg_match( '/(.*)\((.*):(\d+)\)/i', (string) $slug, $matches ); + if ( isset( $matches[3] ) ) { + $term = get_term_by( 'term_taxonomy_id', $matches[3] ); + } else { + // Fallback to fetching the category as this was the original format. + $term = get_term_by( 'name', $slug, 'category' ); + } + if ( isset( $term->term_taxonomy_id ) ) { + $tax_ids[] = $term->term_taxonomy_id; + $tax_slugs[] = "{$term->name} ({$term->taxonomy}:{$term->term_taxonomy_id})"; + } + } + + $settings[ $target_key ] = isset( $tax_ids ) ? join( ',', $tax_ids ) : ''; + $settings[ $source_key ] = isset( $tax_slugs ) ? self::str_putcsv( $tax_slugs ) : ''; + } + } +} diff --git a/includes/admin/settings/class-settings.php b/includes/admin/settings/class-settings.php new file mode 100644 index 0000000..346b4a0 --- /dev/null +++ b/includes/admin/settings/class-settings.php @@ -0,0 +1,714 @@ +settings_key = 'wzkb_settings'; + self::$prefix = 'wzkb'; + $this->menu_slug = 'wzkb-settings'; + + add_action( 'admin_menu', array( $this, 'initialise_settings' ) ); + add_action( 'admin_head', array( $this, 'admin_head' ), 11 ); + add_filter( 'plugin_row_meta', array( $this, 'plugin_row_meta' ), 11, 2 ); + add_filter( 'plugin_action_links_' . plugin_basename( WZKB_PLUGIN_FILE ), array( $this, 'plugin_actions_links' ) ); + add_filter( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ), 99 ); + + add_filter( self::$prefix . '_settings_sanitize', array( $this, 'change_settings_on_save' ), 99 ); + } + + /** + * Initialise the settings API. + * + * @since 2.3.0 + */ + public function initialise_settings() { + $props = array( + 'default_tab' => 'general', + 'help_sidebar' => $this->get_help_sidebar(), + 'help_tabs' => $this->get_help_tabs(), + 'admin_footer_text' => $this->get_admin_footer_text(), + 'menus' => $this->get_menus(), + ); + + $args = array( + 'props' => $props, + 'translation_strings' => $this->get_translation_strings(), + 'settings_sections' => $this->get_settings_sections(), + 'registered_settings' => $this->get_registered_settings(), + 'upgraded_settings' => array(), + ); + + $this->settings_api = new Settings_API( $this->settings_key, self::$prefix, $args ); + } + + /** + * Array containing the translation strings. + * + * @since 1.8.0 + * + * @return array Translation strings. + */ + public function get_translation_strings() { + $strings = array( + 'page_title' => esc_html__( 'Knowledge Base Settings', 'knowledgebase' ), + 'menu_title' => esc_html__( 'Settings', 'knowledgebase' ), + 'page_header' => esc_html__( 'Knowledge Base Settings', 'knowledgebase' ), + 'reset_message' => esc_html__( 'Settings have been reset to their default values. Reload this page to view the updated settings.', 'knowledgebase' ), + 'success_message' => esc_html__( 'Settings updated.', 'knowledgebase' ), + 'save_changes' => esc_html__( 'Save Changes', 'knowledgebase' ), + 'reset_settings' => esc_html__( 'Reset all settings', 'knowledgebase' ), + 'reset_button_confirm' => esc_html__( 'Do you really want to reset all these settings to their default values?', 'knowledgebase' ), + 'checkbox_modified' => esc_html__( 'Modified from default setting', 'knowledgebase' ), + ); + + /** + * Filter the array containing the settings' sections. + * + * @since 2.2.0 + * + * @param array $strings Translation strings. + */ + return apply_filters( self::$prefix . '_translation_strings', $strings ); + } + + /** + * Get the admin menus. + * + * @return array Admin menus. + */ + public function get_menus() { + $menus = array(); + + // Settings menu. + $menus[] = array( + 'settings_page' => true, + 'type' => 'submenu', + 'parent_slug' => 'edit.php?post_type=wz_knowledgebase', + 'page_title' => esc_html__( 'Knowledge Base Settings', 'knowledgebase' ), + 'menu_title' => esc_html__( 'Settings', 'knowledgebase' ), + 'menu_slug' => $this->menu_slug, + ); + + return $menus; + } + + /** + * Array containing the settings' sections. + * + * @since 2.3.0 + * + * @return array Settings array + */ + public static function get_settings_sections() { + $sections = array( + 'general' => __( 'General', 'knowledgebase' ), + 'output' => __( 'Output', 'knowledgebase' ), + 'styles' => __( 'Styles', 'knowledgebase' ), + ); + + /** + * Filter the array containing the settings' sections. + * + * @since 2.2.0 + * + * @param array $sections Array of settings' sections + */ + return apply_filters( self::$prefix . '_settings_sections', $sections ); + } + + + /** + * Retrieve the array of plugin settings + * + * @since 2.3.0 + * + * @return array Settings array + */ + public static function get_registered_settings() { + $settings = array(); + $sections = self::get_settings_sections(); + + foreach ( $sections as $section => $value ) { + $method_name = 'settings_' . $section; + if ( method_exists( __CLASS__, $method_name ) ) { + $settings[ $section ] = self::$method_name(); + } + } + + /** + * Filters the settings array + * + * @since 2.2.0 + * + * @param array $Knowledgebase_setings Settings array + */ + return apply_filters( self::$prefix . '_registered_settings', $settings ); + } + + /** + * Retrieve the array of General settings + * + * @since 2.3.0 + * + * @return array General settings array + */ + public static function settings_general() { + $settings = array( + 'slug_header' => array( + 'id' => 'slug_header', + 'name' => '

' . esc_html__( 'Slug options', 'knowledgebase' ) . '

', + 'desc' => '', + 'type' => 'header', + ), + 'kb_slug' => array( + 'id' => 'kb_slug', + 'name' => esc_html__( 'Knowledge Base slug', 'knowledgebase' ), + 'desc' => esc_html__( 'This will set the opening path of the URL of the knowledge base and is set when registering the custom post type', 'knowledgebase' ), + 'type' => 'text', + 'options' => 'knowledgebase', + ), + 'category_slug' => array( + 'id' => 'category_slug', + 'name' => esc_html__( 'Category slug', 'knowledgebase' ), + 'desc' => esc_html__( 'Each category is a section of the knowledge base. This setting is used when registering the custom category and forms a part of the URL when browsing category archives', 'knowledgebase' ), + 'type' => 'text', + 'options' => 'section', + ), + 'tag_slug' => array( + 'id' => 'tag_slug', + 'name' => esc_html__( 'Tag slug', 'knowledgebase' ), + 'desc' => esc_html__( 'Each article can have multiple tags. This setting is used when registering the custom tag and forms a part of the URL when browsing tag archives', 'knowledgebase' ), + 'type' => 'text', + 'options' => 'kb-tags', + ), + 'cache' => array( + 'id' => 'cache', + 'name' => esc_html__( 'Enable cache', 'knowledgebase' ), + 'desc' => esc_html__( 'Cache the output of the WP_Query lookups to speed up retrieval of the knowledgebase. Recommended for large knowledge bases', 'knowledgebase' ), + 'type' => 'checkbox', + 'options' => false, + ), + 'uninstall_header' => array( + 'id' => 'uninstall_header', + 'name' => '

' . esc_html__( 'Uninstall options', 'knowledgebase' ) . '

', + 'desc' => '', + 'type' => 'header', + 'options' => '', + ), + 'uninstall_options' => array( + 'id' => 'uninstall_options', + 'name' => esc_html__( 'Delete options on uninstall', 'knowledgebase' ), + 'desc' => esc_html__( 'Check this box to delete the settings on this page when the plugin is deleted via the Plugins page in your WordPress Admin', 'knowledgebase' ), + 'type' => 'checkbox', + 'options' => true, + ), + 'uninstall_data' => array( + 'id' => 'uninstall_data', + 'name' => esc_html__( 'Delete all knowledge base posts on uninstall', 'knowledgebase' ), + 'desc' => esc_html__( 'Check this box to delete all the posts, categories and tags created by the plugin. There is no way to restore the data if you choose this option', 'knowledgebase' ), + 'type' => 'checkbox', + 'options' => false, + ), + 'feed_header' => array( + 'id' => 'feed_header', + 'name' => '

' . esc_html__( 'Feed options', 'knowledgebase' ) . '

', + 'desc' => '', + 'type' => 'header', + 'options' => '', + ), + 'include_in_feed' => array( + 'id' => 'include_in_feed', + 'name' => esc_html__( 'Include in feed', 'knowledgebase' ), + 'desc' => esc_html__( 'Adds the knowledge base articles to the main RSS feed for your site', 'knowledgebase' ), + 'type' => 'checkbox', + 'options' => true, + ), + 'disable_kb_feed' => array( + 'id' => 'disable_kb_feed', + 'name' => esc_html__( 'Disable KB feed', 'knowledgebase' ), + /* translators: 1: Opening link tag, 2: Closing link tag. */ + 'desc' => sprintf( esc_html__( 'The knowledge base articles have a default feed. This option will disable the feed. You might need to %1$srefresh your permalinks%2$s when changing this option.', 'knowledgebase' ), '', '' ), + 'type' => 'checkbox', + 'options' => false, + ), + ); + + /** + * Filters the General settings array + * + * @since 2.2.0 + * + * @param array $settings General Settings array + */ + return apply_filters( self::$prefix . '_settings_general', $settings ); + } + + + /** + * Retrieve the array of Output settings + * + * @since 2.3.0 + * + * @return array Output settings array + */ + public static function settings_output() { + + $settings = array( + 'kb_title' => array( + 'id' => 'kb_title', + 'name' => esc_html__( 'Knowledge base title', 'knowledgebase' ), + 'desc' => esc_html__( 'This will be displayed as the title of the archive title as well as on other relevant places.', 'knowledgebase' ), + 'type' => 'text', + 'options' => 'Knowledge Base', + 'field_class' => 'large-text', + ), + 'category_level' => array( + 'id' => 'category_level', + 'name' => esc_html__( 'First section level', 'knowledgebase' ), + 'desc' => esc_html__( 'This option allows you to create multi-level knowledge bases. This works in conjunction with the inbuilt styles. Set to 1 to lay out the top level sections in a grid. Set to 2 to lay out the second level categories in the grid. This is great if you have multiple products and want to create separate knowledge bases for each of them. The default option is 2 and was the behaviour of this plugin before v1.5.0.', 'knowledgebase' ), + 'type' => 'number', + 'options' => '2', + 'size' => 'small', + 'min' => '1', + 'max' => '5', + ), + 'show_article_count' => array( + 'id' => 'show_article_count', + 'name' => esc_html__( 'Show article count', 'knowledgebase' ), + 'desc' => esc_html__( 'If selected, the number of articles will be displayed in an orange circle next to the header. You can override the color by styling wzkb_section_count', 'knowledgebase' ), + 'type' => 'checkbox', + 'options' => false, + ), + 'show_excerpt' => array( + 'id' => 'show_excerpt', + 'name' => esc_html__( 'Show excerpt', 'knowledgebase' ), + 'desc' => esc_html__( 'Select to include the post excerpt after the article link', 'knowledgebase' ), + 'type' => 'checkbox', + 'options' => false, + ), + 'clickable_section' => array( + 'id' => 'clickable_section', + 'name' => esc_html__( 'Link section title', 'knowledgebase' ), + 'desc' => esc_html__( 'If selected, the title of each section of the knowledgebase will be linked to its own page', 'knowledgebase' ), + 'type' => 'checkbox', + 'options' => true, + ), + 'show_empty_sections' => array( + 'id' => 'show_empty_sections', + 'name' => esc_html__( 'Show empty sections', 'knowledgebase' ), + 'desc' => esc_html__( 'If selected, sections with no articles will also be displayed', 'knowledgebase' ), + 'type' => 'checkbox', + 'options' => false, + ), + 'limit' => array( + 'id' => 'limit', + 'name' => esc_html__( 'Max articles per section', 'knowledgebase' ), + 'desc' => esc_html__( 'Enter the number of articles that should be displayed in each section when viewing the knowledge base. After this limit is reached, the footer is displayed with the more link to view the category.', 'knowledgebase' ), + 'type' => 'number', + 'options' => '5', + 'size' => 'small', + 'min' => '1', + 'max' => '500', + ), + 'show_sidebar' => array( + 'id' => 'show_sidebar', + 'name' => esc_html__( 'Show sidebar', 'knowledgebase' ), + 'desc' => esc_html__( 'Add the sidebar of your theme into the inbuilt templates for archive, sections and search. Activate this option if your theme does not already include this.', 'knowledgebase' ), + 'type' => 'checkbox', + 'options' => false, + ), + 'show_related_articles' => array( + 'id' => 'show_related_articles', + 'name' => esc_html__( 'Show related articles', 'knowledgebase' ), + 'desc' => esc_html__( 'Add related articles at the bottom of the knowledge base article. Only works when using the inbuilt template.', 'knowledgebase' ), + 'type' => 'checkbox', + 'options' => true, + ), + ); + + /** + * Filters the Output settings array + * + * @since 2.2.0 + * + * @param array $settings Output Settings array + */ + return apply_filters( self::$prefix . '_settings_output', $settings ); + } + + /** + * Retrieve the array of Styles settings + * + * @since 2.3.0 + * + * @return array Styles settings array + */ + public static function settings_styles() { + $settings = array( + 'include_styles' => array( + 'id' => 'include_styles', + 'name' => esc_html__( 'Include inbuilt styles', 'knowledgebase' ), + 'desc' => esc_html__( 'Uncheck this to disable this plugin from adding the inbuilt styles. You will need to add your own CSS styles if you disable this option', 'knowledgebase' ), + 'type' => 'checkbox', + 'options' => true, + ), + 'columns' => array( + 'id' => 'columns', + 'name' => esc_html__( 'Number of columns', 'knowledgebase' ), + 'desc' => esc_html__( 'Set number of columns to display the knowledge base archives. This is only works if the above option is selected.', 'knowledgebase' ), + 'type' => 'number', + 'options' => '2', + 'size' => 'small', + 'min' => '1', + 'max' => '5', + ), + 'custom_css' => array( + 'id' => 'custom_css', + 'name' => esc_html__( 'Custom CSS', 'knowledgebase' ), + 'desc' => esc_html__( 'Enter any custom valid CSS without any wrapping <style> tags', 'knowledgebase' ), + 'type' => 'css', + 'options' => '', + 'field_class' => 'codemirror_css', + ), + ); + + /** + * Filters the Styles settings array + * + * @since 2.2.0 + * + * @param array $settings Styles settings array + */ + return apply_filters( self::$prefix . '_settings_styles', $settings ); + } + + /** + * Adding WordPress plugin action links. + * + * @since 2.3.0 + * + * @param array $links Array of links. + * @return array + */ + public function plugin_actions_links( $links ) { + + return array_merge( + array( + 'settings' => '' . esc_html__( 'Settings', 'knowledgebase' ) . '', + ), + $links + ); + } + + /** + * Add meta links on Plugins page. + * + * @since 2.3.0 + * + * @param array $links Array of Links. + * @param string $file Current file. + * @return array + */ + public function plugin_row_meta( $links, $file ) { + + if ( false !== strpos( $file, 'knowledgebase.php' ) ) { + $new_links = array( + 'support' => '' . esc_html__( 'Support', 'knowledgebase' ) . '', + 'donate' => '' . esc_html__( 'Donate', 'knowledgebase' ) . '', + 'contribute' => '' . esc_html__( 'Contribute', 'knowledgebase' ) . '', + ); + + $links = array_merge( $links, $new_links ); + } + return $links; + } + + /** + * Get the help sidebar content to display on the plugin settings page. + * + * @since 1.8.0 + */ + public function get_help_sidebar() { + $help_sidebar = + /* translators: 1: Plugin support site link. */ + '

' . sprintf( __( 'For more information or how to get support visit the support site.', 'knowledgebase' ), esc_url( 'https://webberzone.com/support/' ) ) . '

' . + /* translators: 1: WordPress.org support forums link. */ + '

' . sprintf( __( 'Support queries should be posted in the WordPress.org support forums.', 'knowledgebase' ), esc_url( 'https://wordpress.org/support/plugin/knowledgebase' ) ) . '

' . + '

' . sprintf( + /* translators: 1: Github issues link, 2: Github plugin page link. */ + __( 'Post an issue on GitHub (bug reports only).', 'knowledgebase' ), + esc_url( 'https://github.com/WebberZone/knowledgebase/issues' ), + esc_url( 'https://github.com/WebberZone/knowledgebase' ) + ) . '

'; + + /** + * Filter to modify the help sidebar content. + * + * @since 2.3.0 + * + * @param string $help_sidebar Help sidebar content. + */ + return apply_filters( self::$prefix . '_settings_help', $help_sidebar ); + } + + /** + * Get the help tabs to display on the plugin settings page. + * + * @since 2.3.0 + */ + public function get_help_tabs() { + $help_tabs = array( + array( + 'id' => 'wzkb-settings-general', + 'title' => __( 'General', 'knowledgebase' ), + 'content' => + '

' . __( 'This screen provides the basic settings for configuring your knowledge base.', 'knowledgebase' ) . '

' . + '

' . __( 'Set the knowledge base slugs which drive what the urls are for the knowledge base homepage, articles, categories and tags.', 'knowledgebase' ) . '

', + ), + array( + 'id' => 'wzkb-settings-styles', + 'title' => __( 'Styles', 'knowledgebase' ), + 'content' => + '

' . __( 'This screen provides options to control the look and feel of the knowledge base.', 'knowledgebase' ) . '

' . + '

' . __( 'Disable the styles included within the plugin and/or add your own CSS styles to customize this.', 'knowledgebase' ) . '

', + ), + ); + + /** + * Filter to add more help tabs. + * + * @since 2.2.0 + * + * @param array $help_tabs Associative array of help tabs. + */ + return apply_filters( self::$prefix . '_settings_help_tabs', $help_tabs ); + } + + /** + * Add CSS to admin head. + * + * @since 2.2.0 + */ + public function admin_head() { + if ( ! is_customize_preview() ) { + $css = ' + '; + + echo $css; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + } + + /** + * Add footer text on the plugin page. + * + * @since 2.0.0 + */ + public static function get_admin_footer_text() { + return sprintf( + /* translators: 1: Opening achor tag with Plugin page link, 2: Closing anchor tag, 3: Opening anchor tag with review link. */ + __( 'Thank you for using %1$sWebberZone Knowledge_Base%2$s! Please %3$srate us%2$s on %3$sWordPress.org%2$s', 'knowledgebase' ), + '', + '', + '' + ); + } + + /** + * Enqueue scripts and styles. + * + * @since 2.3.0 + * + * @param string $hook Current hook. + */ + public function admin_enqueue_scripts( $hook ) { + + if ( ! isset( $this->settings_api->settings_page ) || $hook !== $this->settings_api->settings_page ) { + return; + } + wp_localize_script( + 'wz-admin-js', + 'wzkb_admin', + array() + ); + wp_enqueue_script( 'knowledgebase-admin-js' ); + wp_enqueue_style( 'knowledgebase-admin-ui-css' ); + wp_enqueue_style( 'wp-spinner' ); + wp_localize_script( + 'knowledgebase-admin-js', + 'wzkb_admin_data', + array( + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'security' => wp_create_nonce( 'wzkb-admin' ), + 'confirm_message' => esc_html__( 'Are you sure you want to clear the cache?', 'knowledgebase' ), + 'success_message' => esc_html__( 'Cache cleared successfully!', 'knowledgebase' ), + 'fail_message' => esc_html__( 'Failed to clear cache. Please try again.', 'knowledgebase' ), + 'request_fail_message' => esc_html__( 'Request failed: ', 'knowledgebase' ), + ) + ); + } + + /** + * Modify settings when they are being saved. + * + * @since 2.3.0 + * + * @param array $settings Settings array. + * @return array Sanitized settings array. + */ + public function change_settings_on_save( $settings ) { + + // Delete the cache. + \WebberZone\Knowledge_Base\Util\Cache::delete(); + + return $settings; + } +} diff --git a/includes/admin/settings/images/btn_donate_LG.gif b/includes/admin/settings/images/btn_donate_LG.gif deleted file mode 100644 index c7d2bf8..0000000 Binary files a/includes/admin/settings/images/btn_donate_LG.gif and /dev/null differ diff --git a/includes/admin/settings/images/fb.png b/includes/admin/settings/images/fb.png deleted file mode 100644 index ade7c5a..0000000 Binary files a/includes/admin/settings/images/fb.png and /dev/null differ diff --git a/includes/admin/settings/images/index.php b/includes/admin/settings/images/index.php deleted file mode 100644 index 8142269..0000000 --- a/includes/admin/settings/images/index.php +++ /dev/null @@ -1 +0,0 @@ - 0) { + editor.replaceSelection(str); + } else { + + var doc = editor.getDoc(); + var cursor = doc.getCursor(); + + var pos = { + line: cursor.line, + ch: cursor.ch + } + + doc.replaceRange(str, pos); + + } + + } + + // Media selector. + $('.insert-codemirror-media').on('click', function (event) { + event.preventDefault(); + + var self = $(this); + var editor = $('#wp-content-editor-container .CodeMirror')[0].CodeMirror; + + function attachmentHtml(props, attachment) { + var caption = attachment.caption, + options, html; + + // If captions are disabled, clear the caption. + if (!wp.media.view.settings.captions) { + delete attachment.caption; + } + + props = wp.media.string.props(props, attachment); + + options = { + id: attachment.id, + post_content: attachment.description, + post_excerpt: caption + }; + + if (props.linkUrl) { + options.url = props.linkUrl; + } + + if ('image' === attachment.type) { + html = wp.media.string.image(props); + + _.each({ + align: 'align', + size: 'image-size', + alt: 'image_alt' + }, function (option, prop) { + if (props[prop]) { + options[option] = props[prop]; + } + }); + } else if ('video' === attachment.type) { + html = wp.media.string.video(props, attachment); + } else if ('audio' === attachment.type) { + html = wp.media.string.audio(props, attachment); + } else { + html = wp.media.string.link(props); + options.post_title = props.title; + } + + return $.ajax({ + type: 'POST', + dataType: 'json', + url: ajaxurl, + data: { + action: 'send-attachment-to-editor', + nonce: wp.media.view.settings.nonce.sendToEditor, + attachment: options, + html: html, + post_id: wp.media.view.settings.post.id + }, + success: function (response) { + //mediaHtml = response.data; + } + }); + } + + // Create the media frame. + var file_frame = wp.media.frames.file_frame = wp.media({ + frame: 'post', + state: 'insert', + multiple: true + }); + + file_frame.on('insert', function () { + var selection = file_frame.state().get('selection'); + + selection.map(function (attachment) { + + var props = file_frame.state().display(attachment).toJSON(); + + $.when(attachmentHtml(props, attachment.toJSON())).done(function (response) { + mediaHtml = response.data; + insertString(editor, mediaHtml); + }); + }); + + }); + + // Finally, open the modal + file_frame.open(); + }); +}); diff --git a/includes/admin/settings/js/media-selector.min.js b/includes/admin/settings/js/media-selector.min.js new file mode 100644 index 0000000..0d05316 --- /dev/null +++ b/includes/admin/settings/js/media-selector.min.js @@ -0,0 +1 @@ +jQuery(document).ready((function(e){e(".insert-codemirror-media").on("click",(function(t){t.preventDefault();e(this);var i=e("#wp-content-editor-container .CodeMirror")[0].CodeMirror;var n=wp.media.frames.file_frame=wp.media({frame:"post",state:"insert",multiple:!0});n.on("insert",(function(){n.state().get("selection").map((function(t){var a=n.state().display(t).toJSON();e.when(function(t,i){var n,a,o=i.caption;return wp.media.view.settings.captions||delete i.caption,t=wp.media.string.props(t,i),n={id:i.id,post_content:i.description,post_excerpt:o},t.linkUrl&&(n.url=t.linkUrl),"image"===i.type?(a=wp.media.string.image(t),_.each({align:"align",size:"image-size",alt:"image_alt"},(function(e,i){t[i]&&(n[e]=t[i])}))):"video"===i.type?a=wp.media.string.video(t,i):"audio"===i.type?a=wp.media.string.audio(t,i):(a=wp.media.string.link(t),n.post_title=t.title),e.ajax({type:"POST",dataType:"json",url:ajaxurl,data:{action:"send-attachment-to-editor",nonce:wp.media.view.settings.nonce.sendToEditor,attachment:n,html:a,post_id:wp.media.view.settings.post.id},success:function(e){}})}(a,t.toJSON())).done((function(e){mediaHtml=e.data,function(e,t){if(e.getSelection().length>0)e.replaceSelection(t);else{var i=e.getDoc(),n=i.getCursor(),a={line:n.line,ch:n.ch};i.replaceRange(t,a)}}(i,mediaHtml)}))}))})),n.open()}))})); \ No newline at end of file diff --git a/includes/admin/settings/js/admin-scripts.js b/includes/admin/settings/js/settings-admin-scripts.js similarity index 51% rename from includes/admin/settings/js/admin-scripts.js rename to includes/admin/settings/js/settings-admin-scripts.js index aa9f9c2..85de344 100644 --- a/includes/admin/settings/js/admin-scripts.js +++ b/includes/admin/settings/js/settings-admin-scripts.js @@ -1,4 +1,4 @@ -jQuery(document).ready(function($) { +jQuery(document).ready(function ($) { // File browser. $('.file-browser').on('click', function (event) { event.preventDefault(); @@ -24,14 +24,14 @@ jQuery(document).ready(function($) { }); // Prompt the user when they leave the page without saving the form. - formmodified=0; + var formmodified = 0; function confirmFormChange() { - formmodified=1; + formmodified = 1; } function confirmExit() { - if ( formmodified == 1 ) { + if (formmodified == 1) { return true; } } @@ -40,30 +40,31 @@ jQuery(document).ready(function($) { formmodified = 0; } - $('form *').change( confirmFormChange ); + $('form').on('change', 'input, textarea, select', confirmFormChange); window.onbeforeunload = confirmExit; - $( "input[name='submit']" ).click(formNotModified); - $( "input[id='search-submit']" ).click(formNotModified); - $( "input[id='doaction']" ).click(formNotModified); - $( "input[id='doaction2']" ).click(formNotModified); - $( "input[name='filter_action']" ).click(formNotModified); + $('input[name="submit"], input#search-submit, input#doaction, input#doaction2, input[name="filter_action"]').on('click', formNotModified); - $( function() { - $( "#post-body-content" ).tabs({ - create: function( event, ui ) { - $( ui.tab.find("a") ).addClass( "nav-tab-active" ); + $(function () { + $("#post-body-content").tabs({ + create: function (event, ui) { + $(ui.tab.find("a")).addClass("nav-tab-active"); }, - activate: function( event, ui ) { - $( ui.oldTab.find("a") ).removeClass( "nav-tab-active" ); - $( ui.newTab.find("a") ).addClass( "nav-tab-active" ); + activate: function (event, ui) { + $(ui.oldTab.find("a")).removeClass("nav-tab-active"); + $(ui.newTab.find("a")).addClass("nav-tab-active"); } }); }); // Initialise ColorPicker. - $( '.color-field' ).each( function ( i, element ) { - $( element ).wpColorPicker(); + $('.color-field').each(function (i, element) { + $(element).wpColorPicker(); }); + + $('.reset-default-thumb').on('click', function () { + $('#wzkb_settings\\[thumb_default\\]').val(wzkb_admin.thumb_default); + }); + }); diff --git a/includes/admin/settings/js/settings-admin-scripts.min.js b/includes/admin/settings/js/settings-admin-scripts.min.js new file mode 100644 index 0000000..17ab277 --- /dev/null +++ b/includes/admin/settings/js/settings-admin-scripts.min.js @@ -0,0 +1 @@ +jQuery(document).ready(function(t){function n(){i=1}function e(){if(1==i)return!0}function a(){i=0}t(".file-browser").on("click",function(n){n.preventDefault();var e=t(this),a=wp.media.frames.file_frame=wp.media({title:e.data("uploader_title"),button:{text:e.data("uploader_button_text")},multiple:!1});a.on("select",function(){attachment=a.state().get("selection").first().toJSON(),e.prev(".file-url").val(attachment.url).change()}),a.open()});var i=0;t("form").on("change","input, textarea, select",n),window.onbeforeunload=e,t('input[name="submit"], input#search-submit, input#doaction, input#doaction2, input[name="filter_action"]').on("click",a),t(function(){t("#post-body-content").tabs({create:function(n,e){t(e.tab.find("a")).addClass("nav-tab-active")},activate:function(n,e){t(e.oldTab.find("a")).removeClass("nav-tab-active"),t(e.newTab.find("a")).addClass("nav-tab-active")}})}),t(".color-field").each(function(n,e){t(e).wpColorPicker()}),t(".reset-default-thumb").on("click",function(){t("#wzkb_settings\\[thumb_default\\]").val(wzkb_admin.thumb_default)})}); \ No newline at end of file diff --git a/includes/admin/settings/js/taxonomy-suggest.js b/includes/admin/settings/js/taxonomy-suggest.js index 3793f4c..c5a9eb9 100644 --- a/includes/admin/settings/js/taxonomy-suggest.js +++ b/includes/admin/settings/js/taxonomy-suggest.js @@ -1,6 +1,6 @@ jQuery(document).ready(function($) { // Function to add auto suggest. - $.fn.wzTagsSuggest = function( options ) { + $.fn.WZTagsSuggest = function( options ) { var cache; var last; var $element = $( this ); @@ -8,7 +8,7 @@ jQuery(document).ready(function($) { options = options || {}; var taxonomy = options.taxonomy || $element.attr( 'data-wp-taxonomy' ) || 'category'; - var tag_search = options.tag_search || $element.attr( 'data-wp-action' ) || 'wz_tag_search'; + var tag_search = options.tag_search || $element.attr( 'data-wp-action' ) || 'wz_tags_search'; delete( options.taxonomy ); delete( options.tag_search ); @@ -103,10 +103,10 @@ jQuery(document).ready(function($) { }; $( '.category_autocomplete' ).each( function ( i, element ) { - $( element ).wzTagsSuggest(); + $( element ).WZTagsSuggest(); }); $('.widget-liquid-right, #customize-controls').on( 'click', '.category_autocomplete', function() { - $( '.category_autocomplete' ).wzTagsSuggest(); + $( '.category_autocomplete' ).WZTagsSuggest(); }); }); diff --git a/includes/admin/settings/js/taxonomy-suggest.min.js b/includes/admin/settings/js/taxonomy-suggest.min.js index 554b49d..cf097e7 100644 --- a/includes/admin/settings/js/taxonomy-suggest.min.js +++ b/includes/admin/settings/js/taxonomy-suggest.min.js @@ -1 +1 @@ -jQuery(document).ready((function($){$.fn.wzTagsSuggest=function(options){var cache,last,$element=$(this),taxonomy=(options=options||{}).taxonomy||$element.attr("data-wp-taxonomy")||"category",tag_search=options.tag_search||$element.attr("data-wp-action")||"wz_tag_search";function split(val){return val.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/)}function extractLast(term){return split(term).pop()}delete options.taxonomy,delete options.tag_search,options=$.extend({minLength:2,position:{my:"left top+2",at:"left bottom",collision:"none"},source:function(request,response){var term;last!==request.term?(term=extractLast(request.term),last!==request.term?($.ajax({type:"POST",dataType:"json",url:ajaxurl,data:{action:tag_search,tax:taxonomy,q:term}}).done((function(data){cache=data,response(data)})),last=request.term):response(cache)):response(cache)},search:function(){var term;if(extractLast(this.value).length<2)return!1},focus:function(event,ui){event.preventDefault()},select:function(event,ui){var terms=split(this.value),val=ui.item.value;return-1!==val.indexOf(",")&&(val='"'+val+'"'),terms.pop(),terms.push(val),terms.push(""),this.value=terms.join(", "),!1}},options),$element.on("keydown",(function(event){event.keyCode===$.ui.keyCode.TAB&&$(this).autocomplete("instance").menu.active&&event.preventDefault()})).autocomplete(options)},$(".category_autocomplete").each((function(i,element){$(element).wzTagsSuggest()})),$(".widget-liquid-right, #customize-controls").on("click",".category_autocomplete",(function(){$(".category_autocomplete").wzTagsSuggest()}))})); \ No newline at end of file +jQuery(document).ready((function(t){t.fn.WZTagsSuggest=function(e){var o,n,a=t(this),u=(e=e||{}).taxonomy||a.attr("data-wp-taxonomy")||"category",c=e.tag_search||a.attr("data-wp-action")||"wz_tags_search";function i(t){return t.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/)}function r(t){return i(t).pop()}delete e.taxonomy,delete e.tag_search,e=t.extend({minLength:2,position:{my:"left top+2",at:"left bottom",collision:"none"},source:function(e,a){var i;n!==e.term?(i=r(e.term),n!==e.term?(t.ajax({type:"POST",dataType:"json",url:ajaxurl,data:{action:c,tax:u,q:i}}).done((function(t){o=t,a(t)})),n=e.term):a(o)):a(o)},search:function(){if(r(this.value).length<2)return!1},focus:function(t,e){t.preventDefault()},select:function(t,e){var o=i(this.value),n=e.item.value;return-1!==n.indexOf(",")&&(n='"'+n+'"'),o.pop(),o.push(n),o.push(""),this.value=o.join(", "),!1}},e),a.on("keydown",(function(e){e.keyCode===t.ui.keyCode.TAB&&t(this).autocomplete("instance").menu.active&&e.preventDefault()})).autocomplete(e)},t(".category_autocomplete").each((function(e,o){t(o).WZTagsSuggest()})),t(".widget-liquid-right, #customize-controls").on("click",".category_autocomplete",(function(){t(".category_autocomplete").WZTagsSuggest()}))})); \ No newline at end of file diff --git a/includes/admin/settings/sidebar.php b/includes/admin/settings/sidebar.php index 210fa8a..1e036e0 100644 --- a/includes/admin/settings/sidebar.php +++ b/includes/admin/settings/sidebar.php @@ -2,73 +2,75 @@ /** * Sidebar * - * @link https://webberzone.com - * @since 1.0.0 - * - * @package WZKB - * @subpackage Admin/Footer + * @package WebberZone\Knowledge_Base */ ?>
-
-

- -
- -
-
+

+
+

+ +
+ +
+
+
-

+

- + +
-
+ \ No newline at end of file diff --git a/includes/autoloader.php b/includes/autoloader.php new file mode 100644 index 0000000..9d58ad8 --- /dev/null +++ b/includes/autoloader.php @@ -0,0 +1,47 @@ + 'render_kb_block', + 'alerts' => 'render_alerts_block', + ); + + foreach ( $blocks as $block_name => $render_callback ) { + register_block_type_from_metadata( + __DIR__ . "/build/$block_name/", + array( + 'render_callback' => array( $this, $render_callback ), + ) + ); + } + } + + /** + * Renders the `knowledgebase/knowledgebase` block on server. + * + * @since 2.3.0 + * + * @param array $attributes The block attributes. + * + * @return string Returns the post content with latest posts added. + */ + public function render_kb_block( $attributes ) { + // Remap selected attributes from JS to PHP. + $mappings = array( + 'show_article_count' => 'showArticleCount', + 'show_excerpt' => 'showExcerpt', + 'clickable_section' => 'hasClickableSection', + 'show_empty_sections' => 'showEmptySections', + 'extra_class' => 'className', + ); + + foreach ( $mappings as $php_attr => $js_attr ) { + if ( isset( $attributes[ $js_attr ] ) ) { + $attributes[ $php_attr ] = $attributes[ $js_attr ]; + } + } + + $arguments = array_merge( + $attributes, + array( + 'is_block' => 1, + ) + ); + + $arguments = wp_parse_args( $attributes['other_attributes'], $arguments ); + + /** + * Filters arguments passed to get_wzkb for the block. + * + * @since 2.0.0 + * + * @param array $arguments Knowledge Base block options array. + * @param array $attributes Block attributes array. + */ + $arguments = apply_filters( 'wzkb_block_options', $arguments, $attributes ); + + return wzkb_knowledge( $arguments ); + } + + /** + * Renders the `knowledgebase/alerts` block on server. + * + * @since 2.3.0 + * + * @param array $attributes The block attributes. + * @param string $content The block content. + * @param array $block The block object. + * + * @return string Returns the post content with latest posts added. + */ + public function render_alerts_block( $attributes, $content, $block ) { + return wp_kses_post( $content ); + } +} diff --git a/includes/blocks/register-blocks.php b/includes/blocks/register-blocks.php deleted file mode 100644 index 981b647..0000000 --- a/includes/blocks/register-blocks.php +++ /dev/null @@ -1,101 +0,0 @@ - 'render_wzkb_block', - ) - ); - // Register Knowledge Base Alerts block. - register_block_type_from_metadata( - __DIR__ . '/build/alerts/', - array( - 'render_callback' => 'render_wzkb_alerts_block', - ) - ); -} -add_action( 'init', 'wzkb_register_blocks' ); - - -/** - * Renders the `knowledgebase/knowledgebase` block on server. - * - * @since 2.0.0 - * - * @param array $attributes The block attributes. - * - * @return string Returns the post content with latest posts added. - */ -function render_wzkb_block( $attributes ) { - - // Remap selected attributes from JS to PHP. - $mappings = array( - 'show_article_count' => 'showArticleCount', - 'show_excerpt' => 'showExcerpt', - 'clickable_section' => 'hasClickableSection', - 'show_empty_sections' => 'showEmptySections', - 'extra_class' => 'className', - ); - - foreach ( $mappings as $php_attr => $js_attr ) { - if ( isset( $attributes[ $js_attr ] ) ) { - $attributes[ $php_attr ] = $attributes[ $js_attr ]; - } - } - - $arguments = array_merge( - $attributes, - array( - 'is_block' => 1, - ) - ); - - $arguments = wp_parse_args( $attributes['other_attributes'], $arguments ); - - /** - * Filters arguments passed to get_wzkb for the block. - * - * @since 2.0.0 - * - * @param array $arguments Knowledge Base block options array. - * @param array $attributes Block attributes array. - */ - $arguments = apply_filters( 'wzkb_block_options', $arguments, $attributes ); - - return wzkb_knowledge( $arguments ); -} - -/** - * Renders the `knowledgebase/alerts` block on server. - * - * @since 2.2.1 - * - * @param array $attributes The block attributes. - * @param string $content The block content. - * @param array $block The block object. - * - * @return string Returns the post content with latest posts added. - */ -function render_wzkb_alerts_block( $attributes, $content, $block ) { - return wp_kses_post( $content ); -} diff --git a/includes/breadcrumbs.php b/includes/breadcrumbs.php deleted file mode 100644 index fe5e36f..0000000 --- a/includes/breadcrumbs.php +++ /dev/null @@ -1,126 +0,0 @@ - ' » ', // Separator. - ); - - // Parse incomming $args into an array and merge it with $defaults. - $args = wp_parse_args( $args, $defaults ); - - // Return if not a WZKB post type archive or single page. - if ( ! is_post_type_archive( 'wz_knowledgebase' ) && ! is_singular( 'wz_knowledgebase' ) && ! is_tax( 'wzkb_category' ) && ! is_tax( 'wzkb_tag' ) ) { - return false; - } - - $output = '
'; - - // First output the link to home page. - $output .= ''; - $output .= esc_html__( 'Home', 'knowledgebase' ); - $output .= ''; - $output .= $args['separator']; - - // Link to the knowledge base. - $output .= '' . wzkb_get_option( 'kb_title' ) . ''; - - // Output the category or tag. - if ( is_tax( 'wzkb_category' ) || is_tax( 'wzkb_tag' ) ) { - $tax = get_queried_object(); - - $output .= $args['separator']; - $output .= wzkb_breadcrumb_tax_loop( $tax, $args ); - } - - // Output link to single post. - if ( is_singular( 'wz_knowledgebase' ) ) { - $post = get_queried_object(); - - $terms = get_the_terms( $post, 'wzkb_category' ); - if ( $terms && ! is_wp_error( $terms ) ) { - $tax = $terms[0]; - $output .= $args['separator']; - $output .= wzkb_breadcrumb_tax_loop( $tax, $args ); - } - - $output .= $args['separator']; - $output .= '' . $post->post_title . ''; - } - - $output .= '
'; // End wzkb_breadcrumb. - $output .= '
'; - - /** - * Filter the formatted shortcode output. - * - * @since 1.6.0 - * - * @param string $output Formatted HTML output - * @param array $args Parameters array - */ - return apply_filters( 'wzkb_get_breadcrumb', $output, $args ); -} - -/** - * Echo the breadcrumb output. - * - * @since 1.6.0 - * - * @param array $args Parameters array. - */ -function wzkb_breadcrumb( $args = array() ) { - echo wzkb_get_breadcrumb( $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -} - -/** - * Generates the HTML for the taxonomy and its children for the breadcrumb. - * - * @since 1.6.0 - * - * @param object $taxonomy Taxonomy object. - * @param array $args Parameters array. - * @return string HTML output - */ -function wzkb_breadcrumb_tax_loop( $taxonomy, $args = array() ) { - - $output = '' . $taxonomy->name . ''; - - if ( ! empty( $taxonomy->parent ) ) { - $output = wzkb_breadcrumb_tax_loop( get_term( $taxonomy->parent, $taxonomy->taxonomy ), $args ) . $args['separator'] . $output; - } - - /** - * Filters the HTML for the taxonomy and its children for the breadcrumb. - * - * @since 1.6.0 - * - * @param string $output HTML output. - * @param object $taxonomy Taxonomy object. - * @param array $args Parameters array. - */ - return apply_filters( 'wzkb_breadcrumb_tax_loop', $output, $taxonomy, $args ); -} diff --git a/includes/class-cpt.php b/includes/class-cpt.php new file mode 100644 index 0000000..0cf1979 --- /dev/null +++ b/includes/class-cpt.php @@ -0,0 +1,201 @@ + $slug, + 'with_front' => false, + 'feeds' => \wzkb_get_option( 'disable_kb_feed' ) ? false : true, + ); + + $ptlabels = array( + 'name' => _x( 'Knowledge Base', 'Post Type General Name', 'knowledgebase' ), + 'singular_name' => _x( 'Knowledge Base', 'Post Type Singular Name', 'knowledgebase' ), + 'menu_name' => __( 'Knowledge Base', 'knowledgebase' ), + 'name_admin_bar' => __( 'Knowledge Base Article', 'knowledgebase' ), + 'parent_item_colon' => __( 'Parent Article', 'knowledgebase' ), + 'all_items' => __( 'All Articles', 'knowledgebase' ), + 'add_new_item' => __( 'Add New Article', 'knowledgebase' ), + 'add_new' => __( 'Add New Article', 'knowledgebase' ), + 'new_item' => __( 'New Article', 'knowledgebase' ), + 'edit_item' => __( 'Edit Article', 'knowledgebase' ), + 'update_item' => __( 'Update Article', 'knowledgebase' ), + 'view_item' => __( 'View Article', 'knowledgebase' ), + 'search_items' => __( 'Search Article', 'knowledgebase' ), + 'not_found' => __( 'Not found', 'knowledgebase' ), + 'not_found_in_trash' => __( 'Not found in Trash', 'knowledgebase' ), + ); + + /** + * Filter the labels of the post type. + * + * @since 1.2.0 + * + * @param array $ptlabels Post type lables + */ + $ptlabels = apply_filters( 'wzkb_post_type_labels', $ptlabels ); + + $ptargs = array( + 'label' => __( 'wz_knowledgebase', 'knowledgebase' ), + 'description' => __( 'Knowledge Base', 'knowledgebase' ), + 'labels' => $ptlabels, + 'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt', 'revisions', 'author', 'custom-fields', 'comments' ), + 'show_in_rest' => true, + 'taxonomies' => array( 'wzkb_category', 'wzkb_tag' ), + 'public' => true, + 'hierarchical' => false, + 'menu_position' => 5, + 'menu_icon' => 'dashicons-book-alt', + 'map_meta_cap' => true, + 'has_archive' => $archives, + 'rewrite' => $rewrite, + ); + + /** + * Filter the arguments passed to register the post type. + * + * @since 1.2.0 + * + * @param array $ptargs Post type arguments + */ + $ptargs = apply_filters( 'wzkb_post_type_args', $ptargs ); + + register_post_type( 'wz_knowledgebase', $ptargs ); + } + + /** + * Register Knowledgebase Custom Taxonomies. + * + * @since 2.3.0 + */ + public static function register_taxonomies() { + $catslug = \wzkb_get_option( 'category_slug', 'section' ); + $tagslug = \wzkb_get_option( 'tag_slug', 'kb-tags' ); + + $args = array( + 'hierarchical' => true, + 'show_admin_column' => true, + 'show_tagcloud' => false, + 'show_in_rest' => true, + 'rewrite' => array( + 'slug' => $catslug, + 'with_front' => true, + 'hierarchical' => true, + ), + ); + + // Now register categories for the Knowledge Base. + $catlabels = array( + 'name' => _x( 'Sections', 'Taxonomy General Name', 'knowledgebase' ), + 'singular_name' => _x( 'Section', 'Taxonomy Singular Name', 'knowledgebase' ), + 'menu_name' => __( 'Sections', 'knowledgebase' ), + 'all_items' => __( 'All Sections', 'knowledgebase' ), + 'parent_item' => __( 'Parent Section', 'knowledgebase' ), + 'parent_item_colon' => __( 'Parent Section:', 'knowledgebase' ), + 'new_item_name' => __( 'New Section Name', 'knowledgebase' ), + 'add_new_item' => __( 'Add New Section', 'knowledgebase' ), + 'edit_item' => __( 'Edit Section', 'knowledgebase' ), + 'update_item' => __( 'Update Section', 'knowledgebase' ), + 'view_item' => __( 'View Section', 'knowledgebase' ), + 'separate_items_with_commas' => __( 'Separate sections with commas', 'knowledgebase' ), + 'add_or_remove_items' => __( 'Add or remove sections', 'knowledgebase' ), + 'choose_from_most_used' => __( 'Choose from the most used', 'knowledgebase' ), + 'popular_items' => __( 'Popular Sections', 'knowledgebase' ), + 'search_items' => __( 'Search Sections', 'knowledgebase' ), + 'not_found' => __( 'Not Found', 'knowledgebase' ), + 'no_terms' => __( 'No sections', 'knowledgebase' ), + 'items_list' => __( 'Sections list', 'knowledgebase' ), + 'items_list_navigation' => __( 'Sections list navigation', 'knowledgebase' ), + ); + + /** + * Filter the labels of the custom categories. + * + * @since 1.2.0 + * + * @param array $catlabels Category labels + */ + $args['labels'] = apply_filters( 'wzkb_cat_labels', $catlabels ); + + register_taxonomy( + 'wzkb_category', + array( 'wz_knowledgebase' ), + /** + * Filter the arguments of the custom categories. + * + * @since 1.2.0 + * + * @param array $catlabels Category labels + */ + apply_filters( 'wzkb_cat_args', $args ) + ); + + // Now register tags for the Knowledge Base. + $taglabels = array( + 'name' => _x( 'Tags', 'Taxonomy General Name', 'knowledgebase' ), + 'singular_name' => _x( 'Tag', 'Taxonomy Singular Name', 'knowledgebase' ), + 'menu_name' => __( 'Tags', 'knowledgebase' ), + ); + + /** + * Filter the labels of the custom tags. + * + * @since 1.2.0 + * + * @param array $taglabels Tags labels + */ + $args['labels'] = apply_filters( 'wzkb_tag_labels', $taglabels ); + + $args['hierarchical'] = false; + $args['show_tagcloud'] = true; + $args['rewrite']['slug'] = $tagslug; + + register_taxonomy( + 'wzkb_tag', + array( 'wz_knowledgebase' ), + /** + * Filter the arguments of the custom tags. + * + * @since 1.2.0 + * + * @param array $args Tag arguments + */ + apply_filters( 'wzkb_tag_args', $args ) + ); + } +} diff --git a/includes/class-main.php b/includes/class-main.php new file mode 100644 index 0000000..c534f45 --- /dev/null +++ b/includes/class-main.php @@ -0,0 +1,206 @@ +init(); + } + + return self::$instance; + } + + /** + * A dummy constructor. + * + * @since 2.3.0 + */ + private function __construct() { + // Do nothing. + } + + /** + * Initializes the plugin. + * + * @since 2.3.0 + */ + private function init() { + $this->cpt = new CPT(); + $this->language = new Frontend\Language_Handler(); + $this->template_handler = new Frontend\Template_Handler(); + $this->styles = new Frontend\Styles_Handler(); + $this->display = new Frontend\Display(); + $this->related_articles = new Frontend\Related(); + $this->search = new Frontend\Search(); + $this->shortcodes = new Frontend\Shortcodes(); + $this->feed = new Frontend\Feed(); + $this->blocks = new Blocks\Blocks(); + + $this->hooks(); + + if ( is_admin() ) { + $this->admin = new Admin\Admin(); + } + } + + /** + * Run the hooks. + * + * @since 2.3.0 + */ + public function hooks() { + add_action( 'init', array( $this, 'initiate_plugin' ) ); + add_action( 'widgets_init', array( $this, 'register_widgets' ) ); + } + + /** + * Initialise the plugin translations and media. + * + * @since 2.3.0 + */ + public function initiate_plugin() { + Frontend\Media_Handler::add_image_sizes(); + } + + /** + * Initialise the Better Search widgets. + * + * @since 2.3.0 + */ + public function register_widgets() { + register_widget( '\WebberZone\Knowledge_Base\Widgets\Articles_Widget' ); + register_widget( '\WebberZone\Knowledge_Base\Widgets\Sections_Widget' ); + register_widget( '\WebberZone\Knowledge_Base\Widgets\Breadcrumb_Widget' ); + } +} diff --git a/includes/custom-post-type.php b/includes/custom-post-type.php deleted file mode 100644 index 13963a1..0000000 --- a/includes/custom-post-type.php +++ /dev/null @@ -1,191 +0,0 @@ - $slug, - 'with_front' => false, - 'feeds' => wzkb_get_option( 'disable_kb_feed' ) ? false : true, - ); - - $ptlabels = array( - 'name' => _x( 'Knowledge Base', 'Post Type General Name', 'knowledgebase' ), - 'singular_name' => _x( 'Knowledge Base', 'Post Type Singular Name', 'knowledgebase' ), - 'menu_name' => __( 'Knowledge Base', 'knowledgebase' ), - 'name_admin_bar' => __( 'Knowledge Base Article', 'knowledgebase' ), - 'parent_item_colon' => __( 'Parent Article', 'knowledgebase' ), - 'all_items' => __( 'All Articles', 'knowledgebase' ), - 'add_new_item' => __( 'Add New Article', 'knowledgebase' ), - 'add_new' => __( 'Add New Article', 'knowledgebase' ), - 'new_item' => __( 'New Article', 'knowledgebase' ), - 'edit_item' => __( 'Edit Article', 'knowledgebase' ), - 'update_item' => __( 'Update Article', 'knowledgebase' ), - 'view_item' => __( 'View Article', 'knowledgebase' ), - 'search_items' => __( 'Search Article', 'knowledgebase' ), - 'not_found' => __( 'Not found', 'knowledgebase' ), - 'not_found_in_trash' => __( 'Not found in Trash', 'knowledgebase' ), - ); - - /** - * Filter the labels of the post type. - * - * @since 1.2.0 - * - * @param array $ptlabels Post type lables - */ - $ptlabels = apply_filters( 'wzkb_post_type_labels', $ptlabels ); - - $ptargs = array( - 'label' => __( 'wz_knowledgebase', 'knowledgebase' ), - 'description' => __( 'Knowledge Base', 'knowledgebase' ), - 'labels' => $ptlabels, - 'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt', 'revisions', 'author', 'custom-fields', 'comments' ), - 'show_in_rest' => true, - 'taxonomies' => array( 'wzkb_category', 'wzkb_tag' ), - 'public' => true, - 'hierarchical' => false, - 'menu_position' => 5, - 'menu_icon' => 'dashicons-book-alt', - 'map_meta_cap' => true, - 'has_archive' => $archives, - 'rewrite' => $rewrite, - ); - - /** - * Filter the arguments passed to register the post type. - * - * @since 1.2.0 - * - * @param array $ptargs Post type arguments - */ - $ptargs = apply_filters( 'wzkb_post_type_args', $ptargs ); - - register_post_type( 'wz_knowledgebase', $ptargs ); -} -add_action( 'init', 'wzkb_register_post_type' ); - - -/** - * Register Knowledgebase Custom Taxonomies. - * - * @since 1.0.0 - */ -function wzkb_register_taxonomies() { - - $catslug = wzkb_get_option( 'category_slug', 'section' ); - $tagslug = wzkb_get_option( 'tag_slug', 'kb-tags' ); - - $args = array( - 'hierarchical' => true, - 'show_admin_column' => true, - 'show_tagcloud' => false, - 'show_in_rest' => true, - 'rewrite' => array( - 'slug' => $catslug, - 'with_front' => true, - 'hierarchical' => true, - ), - ); - - // Now register categories for the Knowledge Base. - $catlabels = array( - 'name' => _x( 'Sections', 'Taxonomy General Name', 'knowledgebase' ), - 'singular_name' => _x( 'Section', 'Taxonomy Singular Name', 'knowledgebase' ), - 'menu_name' => __( 'Sections', 'knowledgebase' ), - 'all_items' => __( 'All Sections', 'knowledgebase' ), - 'parent_item' => __( 'Parent Section', 'knowledgebase' ), - 'parent_item_colon' => __( 'Parent Section:', 'knowledgebase' ), - 'new_item_name' => __( 'New Section Name', 'knowledgebase' ), - 'add_new_item' => __( 'Add New Section', 'knowledgebase' ), - 'edit_item' => __( 'Edit Section', 'knowledgebase' ), - 'update_item' => __( 'Update Section', 'knowledgebase' ), - 'view_item' => __( 'View Section', 'knowledgebase' ), - 'separate_items_with_commas' => __( 'Separate sections with commas', 'knowledgebase' ), - 'add_or_remove_items' => __( 'Add or remove sections', 'knowledgebase' ), - 'choose_from_most_used' => __( 'Choose from the most used', 'knowledgebase' ), - 'popular_items' => __( 'Popular Sections', 'knowledgebase' ), - 'search_items' => __( 'Search Sections', 'knowledgebase' ), - 'not_found' => __( 'Not Found', 'knowledgebase' ), - 'no_terms' => __( 'No sections', 'knowledgebase' ), - 'items_list' => __( 'Sections list', 'knowledgebase' ), - 'items_list_navigation' => __( 'Sections list navigation', 'knowledgebase' ), - ); - - /** - * Filter the labels of the custom categories. - * - * @since 1.2.0 - * - * @param array $catlabels Category labels - */ - $args['labels'] = apply_filters( 'wzkb_cat_labels', $catlabels ); - - register_taxonomy( - 'wzkb_category', - array( 'wz_knowledgebase' ), - /** - * Filter the arguments of the custom categories. - * - * @since 1.2.0 - * - * @param array $catlabels Category labels - */ - apply_filters( 'wzkb_cat_args', $args ) - ); - - // Now register tags for the Knowledge Base. - $taglabels = array( - 'name' => _x( 'Tags', 'Taxonomy General Name', 'knowledgebase' ), - 'singular_name' => _x( 'Tag', 'Taxonomy Singular Name', 'knowledgebase' ), - 'menu_name' => __( 'Tags', 'knowledgebase' ), - ); - - /** - * Filter the labels of the custom tags. - * - * @since 1.2.0 - * - * @param array $taglabels Tags labels - */ - $args['labels'] = apply_filters( 'wzkb_tag_labels', $taglabels ); - - $args['hierarchical'] = false; - $args['show_tagcloud'] = true; - $args['rewrite']['slug'] = $tagslug; - - register_taxonomy( - 'wzkb_tag', - array( 'wz_knowledgebase' ), - /** - * Filter the arguments of the custom tags. - * - * @since 1.2.0 - * - * @param array $args Tag arguments - */ - apply_filters( 'wzkb_tag_args', $args ) - ); -} -add_action( 'init', 'wzkb_register_taxonomies' ); diff --git a/includes/deprecated.php b/includes/deprecated.php deleted file mode 100644 index 32ed35c..0000000 --- a/includes/deprecated.php +++ /dev/null @@ -1,27 +0,0 @@ - ' » ', // Separator. + ); + + // Parse incomming $args into an array and merge it with $defaults. + $args = wp_parse_args( $args, $defaults ); + + // Return if not a WZKB post type archive or single page. + if ( ! is_post_type_archive( 'wz_knowledgebase' ) && ! is_singular( 'wz_knowledgebase' ) && ! is_tax( 'wzkb_category' ) && ! is_tax( 'wzkb_tag' ) ) { + return false; + } + + $output = '
'; + + // First output the link to home page. + $output .= ''; + $output .= esc_html__( 'Home', 'knowledgebase' ); + $output .= ''; + $output .= $args['separator']; + + // Link to the knowledge base. + $output .= '' . wzkb_get_option( 'kb_title' ) . ''; + + // Output the category or tag. + if ( is_tax( 'wzkb_category' ) || is_tax( 'wzkb_tag' ) ) { + $tax = get_queried_object(); + + $output .= $args['separator']; + $output .= self::get_hierarchical_term_trail( $tax, $args ); + } + + // Output link to single post. + if ( is_singular( 'wz_knowledgebase' ) ) { + $post = get_queried_object(); + + $terms = get_the_terms( $post, 'wzkb_category' ); + if ( $terms && ! is_wp_error( $terms ) ) { + $tax = $terms[0]; + $output .= $args['separator']; + $output .= self::get_hierarchical_term_trail( $tax, $args ); + } + + $output .= $args['separator']; + $output .= '' . $post->post_title . ''; + } + + $output .= '
'; // End wzkb_breadcrumb. + $output .= '
'; + + /** + * Filter the formatted shortcode output. + * + * @since 1.6.0 + * + * @param string $output Formatted HTML output + * @param array $args Parameters array + */ + return apply_filters( 'wzkb_get_breadcrumb', $output, $args ); + } + + /** + * Generates the HTML for the taxonomy and its children for the breadcrumb. + * + * @since 2.3.0 + * + * @param \WP_Term $taxonomy Taxonomy object. + * @param array $args Parameters array. + * @return string HTML output + */ + private static function get_hierarchical_term_trail( \WP_Term $taxonomy, $args = array() ) { + + $defaults = array( + 'separator' => ' » ', // Separator. + ); + + // Parse incomming $args into an array and merge it with $defaults. + $args = wp_parse_args( $args, $defaults ); + + $output = '' . $taxonomy->name . ''; + + if ( ! empty( $taxonomy->parent ) ) { + $output = self::get_hierarchical_term_trail( + get_term( $taxonomy->parent, $taxonomy->taxonomy ), + $args + ) . $args['separator'] . $output; + } + + return $output; + } +} diff --git a/includes/frontend/class-display.php b/includes/frontend/class-display.php new file mode 100644 index 0000000..08926ab --- /dev/null +++ b/includes/frontend/class-display.php @@ -0,0 +1,423 @@ + 0, + 'is_shortcode' => false, + 'is_block' => false, + 'extra_class' => '', + 'show_article_count' => \wzkb_get_option( 'show_article_count' ), + 'show_excerpt' => \wzkb_get_option( 'show_excerpt' ), + 'clickable_section' => \wzkb_get_option( 'clickable_section' ), + 'show_empty_sections' => \wzkb_get_option( 'show_empty_sections' ), + 'limit' => \wzkb_get_option( 'limit' ), + 'columns' => \wzkb_get_option( 'columns' ), + ); + + $args = wp_parse_args( $args, $defaults ); + + // Set defaults if variables are empty. + $args['limit'] = ( ! empty( absint( $args['limit'] ) ) ) ? absint( $args['limit'] ) : \wzkb_get_option( 'limit' ); + $args['columns'] = ( ! empty( absint( $args['columns'] ) ) ) ? absint( $args['columns'] ) : \wzkb_get_option( 'columns' ); + + // Set default classes. + $classes = array(); + $classes[] = $args['extra_class']; + $classes[] = $args['is_shortcode'] ? 'wzkb_shortcode' : ''; + $classes[] = $args['is_block'] ? 'wzkb_block' : ''; + $div_classes = implode( ' ', $classes ); + + /** + * Filter the classes added to the div wrapper of the Knowledge Base. + * + * @since 2.0.0 + * + * @param string $div_classes String with the classes of the div wrapper. + */ + $div_classes = apply_filters( 'wzkb_div_class', $div_classes ); + + $output = '
'; + + // Are we trying to display a category? + $category = intval( $args['category'] ); + + // If $category = -1, then get the current term object and set the category to the term ID. + if ( -1 === $category ) { + $term = get_queried_object(); + if ( isset( $term->term_id ) ) { + $category = $term->term_id; + } + } + + $level = $category > 0 ? 1 : 0; + $term_id = $category > 0 ? $category : 0; + $nested_wrapper = isset( $args['nested_wrapper'] ) ? $args['nested_wrapper'] : true; + + $output .= self::get_knowledge_base_loop( $term_id, $level, $nested_wrapper, $args ); + + $output .= '
'; // End wzkb_section. + $output .= '
'; + + /** + * Filter the formatted output. + * + * @since 1.0.0 + * + * @param string $output Formatted HTML output. + * @param array $args Parameters array. + */ + return apply_filters( 'wzkb_knowledge', $output, $args ); + } + + /** + * Creates the knowledge base loop. + * + * @since 2.3.0 + * + * @param int $term_id Term ID. + * @param int $level Level of the loop. + * @param bool $nested Run recursive loops before closing HTML wrappers. + * @param array $args Parameters array. + * @return string Formatted output. + */ + public static function get_knowledge_base_loop( $term_id, $level, $nested = true, $args = array() ) { + $divclasses = array( 'wzkb_section', 'wzkb-section-level-' . $level ); + $category_level = (int) \wzkb_get_option( 'category_level' ); + + if ( ( $category_level - 1 ) === $level ) { + $divclasses[] = 'section group'; + } elseif ( $category_level === $level ) { + $divclasses[] = 'col span_1_of_' . $args['columns']; + } + + /** + * Filter to add more classes if needed. + * + * @since 1.1.0 + * + * @param array $divclasses Current array of classes. + * @param int $level Level of the loop. + * @param int $term_id Term ID. + */ + $divclasses = apply_filters( 'wzkb_loop_div_class', $divclasses, $level, $term_id ); + + $output = '
'; + + $term = get_term( $term_id, 'wzkb_category' ); + + if ( ! empty( $term ) && ! is_wp_error( $term ) ) { + $output .= self::get_article_header( $term, $level, $args ); + $output .= self::get_posts_by_term( $term, $level, $args ); + } elseif ( $level > 0 ) { + /* translators: Section ID. */ + return sprintf( __( '%s is not enter a valid section ID', 'knowledgebase' ), $term_id ); + } + + $output .= '
'; + + // Get Knowledge Base Sections. + $sections = get_terms( + array( + 'taxonomy' => 'wzkb_category', + 'orderby' => 'slug', + 'hide_empty' => $args['show_empty_sections'] ? 0 : 1, + 'parent' => $term_id, + ) + ); + + if ( ! $nested ) { + $output .= '
'; // End wzkb_section_wrapper. + $output .= '
'; // End wzkb_section. + } + + if ( ! empty( $sections ) && ! is_wp_error( $sections ) ) { + ++$level; + + foreach ( $sections as $section ) { + $output .= self::get_knowledge_base_loop( $section->term_id, $level, $nested, $args ); + } + } + + if ( $nested ) { + $output .= ''; // End wzkb_section_wrapper. + $output .= ''; // End wzkb_section. + } + + return $output; + } + + /** + * Returns query results for a specific term. + * + * @since 2.3.0 + * + * @param \WP_Term $term The Term. + * @return \WP_Query Query results for the given term. + */ + public static function get_term_posts( $term ) { + // Get the term children for the current term. + $termchildren = get_term_children( $term->term_id, 'wzkb_category' ); + + // Get all the posts for the current term excluding posts located in its child terms. + $args = array( + 'posts_per_page' => -1, + 'tax_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query + 'relation' => 'AND', + array( + 'taxonomy' => 'wzkb_category', + 'field' => 'id', + 'terms' => $term->term_id, + ), + array( + 'taxonomy' => 'wzkb_category', + 'field' => 'id', + 'terms' => $termchildren, + 'operator' => 'NOT IN', + ), + ), + ); + + // Support caching to speed up retrieval. + if ( ! empty( \wzkb_get_option( 'cache' ) ) ) { + $meta_key = Cache::get_key( $args ); + $query = get_term_meta( $term->term_id, $meta_key, true ); + } + + if ( empty( $query ) ) { + $query = new \WP_Query( $args ); + } + + // Support caching to speed up retrieval. + if ( ! empty( \wzkb_get_option( 'cache' ) ) ) { + add_term_meta( $term->term_id, $meta_key, $query, true ); + } + + return $query; + } + + /** + * Formatted output of posts for a given term. + * + * @since 2.3.0 + * + * @param \WP_Term $term Current term. + * @param int $level Current level in the recursive loop. + * @param array $args Parameters array. + * @return string Formatted output of posts for a given term. + */ + public static function get_posts_by_term( $term, $level, $args = array() ) { + $output = ''; + $query = self::get_term_posts( $term ); + + if ( $query->have_posts() ) { + $output .= self::get_articles_loop( $term, $level, $query, $args ); + $output .= self::get_article_footer( $term, $level, $query, $args ); + + wp_reset_postdata(); + } + + return $output; + } + + /** + * Header of the articles list. + * + * @since 2.3.0 + * + * @param \WP_Term $term Current term. + * @param int $level Current level in the recursive loop. + * @param array $args Parameters array. + * @return string Formatted header output. + */ + public static function get_article_header( $term, $level, $args = array() ) { + $output = '

'; + + if ( $args['clickable_section'] ) { + $output .= '' . $term->name . ''; + } else { + $output .= $term->name; + } + + if ( $level >= (int) \wzkb_get_option( 'category_level' ) && $args['show_article_count'] ) { + $output .= '
' . $term->count . '
'; + } + + $output .= '

'; + + return $output; + } + + /** + * Creates the list of articles for a particular query results object. + * + * @since 2.3.0 + * + * @param \WP_Term $term Current term. + * @param int $level Current level in the recursive loop. + * @param \WP_Query $query Query results object. + * @param array $args Parameters array. + * @return string Formatted articles list. + */ + public static function get_articles_loop( $term, $level, $query, $args = array() ) { + $limit = 0; + $output = '
    '; + + while ( $query->have_posts() ) : + $query->the_post(); + + $output .= '
  • '; + $output .= '' . get_the_title() . ''; + + if ( $args['show_excerpt'] ) { + $output .= '
    ' . get_the_excerpt() . '
    '; + } + + $output .= '
  • '; + + ++$limit; + + if ( $limit >= $args['limit'] && ! is_tax( 'wzkb_category', $term->term_id ) ) { + break; + } + endwhile; + + $output .= '
'; + + return $output; + } + + /** + * Footer of the articles list. + * + * @since 2.3.0 + * + * @param \WP_Term $term Current term. + * @param int $level Current level in the recursive loop. + * @param \WP_Query $query Query results object. + * @param array $args Parameters array. + * @return string Formatted footer output. + */ + public static function get_article_footer( $term, $level, $query, $args = array() ) { + $output = ''; + + if ( $query->found_posts > $args['limit'] && ! is_tax( 'wzkb_category', $term->term_id ) ) { + $output .= sprintf( + '', + esc_html__( 'Read more articles in ', 'knowledgebase' ), + esc_url( get_term_link( $term ) ), + esc_html( $term->name ) + ); + } + + return $output; + } + + /** + * Get a hierarchical list of WZ Knowledge Base sections. + * + * @since 2.3.0 + * + * @param int $term_id Term ID. + * @param int $level Level of the loop. + * @param array $args Array of arguments. + * @return string HTML output with the categories. + */ + public static function get_categories_list( $term_id, $level = 0, $args = array() ) { + $defaults = array( + 'depth' => 0, // Depth of nesting. + 'before_li_item' => '', // Before list item - just after
  • . + 'after_li_item' => '', // Before list item - just before
  • . + ); + + $args = wp_parse_args( $args, $defaults ); + + // Get Knowledge Base Sections. + $sections = get_terms( + array( + 'taxonomy' => 'wzkb_category', + 'orderby' => 'slug', + 'hide_empty' => \wzkb_get_option( 'show_empty_sections' ) ? 0 : 1, + 'parent' => $term_id, + ) + ); + + $output = ''; + + if ( ! empty( $sections ) && ! is_wp_error( $sections ) ) { + $output .= '
      '; + ++$level; + + foreach ( $sections as $term ) { + $term_link = get_term_link( $term ); + + // If there was an error, continue to the next term. + if ( is_wp_error( $term_link ) ) { + continue; + } + + $output .= '
    • ' . $args['before_li_item']; + $output .= '' . esc_html( $term->name ) . ''; + $output .= self::get_categories_list( $term->term_id, $level, $args ); + $output .= $args['after_li_item'] . '
    • '; + + // Exit the loop if we are at the depth. + if ( 0 < $args['depth'] && $level >= $args['depth'] ) { + break; + } + } + + $output .= '
    '; + } + + return $output; + } +} diff --git a/includes/frontend/class-feed.php b/includes/frontend/class-feed.php new file mode 100644 index 0000000..744004d --- /dev/null +++ b/includes/frontend/class-feed.php @@ -0,0 +1,50 @@ + '', + 'size' => 'thumbnail', + 'thumb_meta' => 'post-image', + 'thumb_html' => 'html', + 'thumb_default' => '', + 'thumb_default_show' => true, + 'scan_images' => false, + 'class' => self::$prefix . '_thumb', + ); + + // Parse incomming $args into an array and merge it with $defaults. + $args = wp_parse_args( $args, $defaults ); + + $result = get_post( $args['post'] ); + + if ( empty( $result ) ) { + return ''; + } + + if ( is_string( $args['size'] ) ) { + list( $args['thumb_width'], $args['thumb_height'] ) = self::get_thumb_size( $args['size'] ); + } else { + $args['thumb_width'] = $args['size'][0]; + $args['thumb_height'] = $args['size'][1]; + $args['size'] = self::get_appropriate_image_size( $args['size'][0], $args['size'][1] ); + } + + $post_title = esc_attr( $result->post_title ); + + $output = ''; + $postimage = ''; + $pick = ''; + $attachment_id = ''; + + // Let's start fetching the thumbnail. First place to look is in the post meta defined in the Settings page. + $postimage = get_post_meta( $result->ID, $args['thumb_meta'], true ); + $postimage = filter_var( $postimage, FILTER_VALIDATE_URL ); + $pick = 'meta'; + if ( $postimage ) { + $attachment_id = self::get_attachment_id_from_url( $postimage ); + + $postthumb = wp_get_attachment_image_src( $attachment_id, $args['size'] ); + if ( false !== $postthumb ) { + list( $postimage, $args['thumb_width'], $args['thumb_height'] ) = $postthumb; + $pick .= 'correct'; + } + } + + // If there is no thumbnail found, check the post thumbnail. + if ( ! $postimage ) { + if ( false !== get_post_thumbnail_id( $result ) ) { + $attachment_id = ( 'attachment' === $result->post_type ) ? $result->ID : get_post_thumbnail_id( $result ); + + $postthumb = wp_get_attachment_image_src( $attachment_id, $args['size'] ); + if ( false !== $postthumb ) { + list( $postimage, $args['thumb_width'], $args['thumb_height'] ) = $postthumb; + $pick = 'featured'; + } + } + $pick = 'featured'; + } + + // If there is no thumbnail found, fetch the first image in the post, if enabled. + if ( ! $postimage && $args['scan_images'] ) { + + /** + * Filters the post content that is used to scan for images. + * + * A filter function can be tapped into this to execute shortcodes, modify content, etc. + * + * @since 3.1.0 + * + * @param string $post_content Post content + * @param \WP_Post $result Post Object + */ + $post_content = apply_filters( self::$prefix . '_thumb_post_content', $result->post_content, $result ); + + preg_match_all( '//i', $post_content, $matches ); + if ( isset( $matches[1][0] ) && $matches[1][0] ) { // any image there? + $postimage = $matches[1][0]; // we need the first one only! + } + $pick = 'first'; + if ( $postimage ) { + $attachment_id = self::get_attachment_id_from_url( $postimage ); + + $postthumb = wp_get_attachment_image_src( $attachment_id, $args['size'] ); + if ( false !== $postthumb ) { + list( $postimage, $args['thumb_width'], $args['thumb_height'] ) = $postthumb; + $pick .= 'correct'; + } + } + } + + // If there is no thumbnail found, fetch the first child image. + if ( ! $postimage ) { + $postimage = self::get_first_image( $result->ID, $args['thumb_width'], $args['thumb_height'] ); // Get the first image. + $pick = 'firstchild'; + } + + // If no other thumbnail set, try to get the custom video thumbnail set by the Video Thumbnails plugin. + if ( ! $postimage ) { + $postimage = get_post_meta( $result->ID, '_video_thumbnail', true ); + $pick = 'video_thumb'; + } + + // If no thumb found and settings permit, use default thumb. + if ( ! $postimage && $args['thumb_default_show'] ) { + $postimage = $args['thumb_default']; + $pick = 'default_thumb'; + } + + // If no thumb found, use site icon. + if ( ! $postimage ) { + $postimage = get_site_icon_url( max( $args['thumb_width'], $args['thumb_height'] ) ); + $pick = 'site_icon_max'; + } + + if ( ! $postimage ) { + $postimage = get_site_icon_url( min( $args['thumb_width'], $args['thumb_height'] ) ); + $pick = 'site_icon_min'; + } + + // Hopefully, we've found a thumbnail by now. If so, run it through the custom filter, check for SSL and create the image tag. + if ( $postimage ) { + + /** + * Filters the thumbnail image URL. + * + * Use this filter to modify the thumbnail URL that is automatically created + * + * @since 2.3.0 + * + * @param string $postimage URL of the thumbnail image + * @param array $args Arguments array. + * @param \WP_Post $result Post Object + */ + $postimage = apply_filters( self::$prefix . '_thumb_url', $postimage, $args, $result ); + + if ( is_ssl() ) { + $postimage = preg_replace( '~http://~', 'https://', $postimage ); + } + + $class = self::$prefix . "_{$pick} {$args['class']} {$args['size']}"; + + if ( empty( $attachment_id ) && ! in_array( $pick, array( 'video_thumb', 'default_thumb', 'site_icon_max', 'site_icon_min' ), true ) ) { + $attachment_id = self::get_attachment_id_from_url( $postimage ); + } + + /** + * Flag to use the image's alt text as the thumbnail alt text. + * + * @since 2.3.0 + * + * @param bool $use_image_alt Flag to use the image's alt text as the thumbnail alt text. + */ + $use_image_alt = apply_filters( self::$prefix . '_thumb_use_image_alt', true ); + + /** + * Flag to use the post title as the thumbnail alt text if no alt text is found. + * + * @since 2.3.0 + * + * @param bool $alt_fallback Flag to use the post title as the thumbnail alt text if no alt text is found. + */ + $alt_fallback = apply_filters( self::$prefix . '_thumb_alt_fallback_post_title', true ); + + if ( ! empty( $attachment_id ) && $use_image_alt ) { + $alt = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ); + } + + // If empty alt then try to get the title of the attachment. + if ( empty( $alt ) && ! empty( $attachment_id ) ) { + $alt = get_the_title( $attachment_id ); + } + + if ( empty( $alt ) ) { + $alt = $alt_fallback ? $post_title : ''; + } + + /** + * Filters the thumbnail classes and allows a filter function to add any more classes if needed. + * + * @since 2.3.0 + * + * @param string $class Thumbnail Class + */ + $attr['class'] = apply_filters( self::$prefix . '_thumb_class', $class ); + + /** + * Filters the thumbnail alt. + * + * @since 2.3.0 + * + * @param string $alt Thumbnail alt attribute + */ + $attr['alt'] = apply_filters( self::$prefix . '_thumb_alt', $alt ); + + /** + * Filters the thumbnail title. + * + * @since 2.3.0 + * + * @param string $post_title Thumbnail title attribute + */ + $attr['title'] = apply_filters( self::$prefix . '_thumb_title', $post_title ); + + $attr['thumb_html'] = $args['thumb_html']; + $attr['thumb_width'] = $args['thumb_width']; + $attr['thumb_height'] = $args['thumb_height']; + + $output .= self::get_image_html( $postimage, $attr ); + + if ( function_exists( 'wp_img_tag_add_srcset_and_sizes_attr' ) && ! empty( $attachment_id ) ) { + $output = wp_img_tag_add_srcset_and_sizes_attr( $output, self::$prefix . '_thumbnail', $attachment_id ); + } + + if ( function_exists( 'wp_img_tag_add_loading_optimization_attrs' ) ) { + $output = wp_img_tag_add_loading_optimization_attrs( $output, self::$prefix . '_thumbnail' ); + } + } + + /** + * Filters post thumbnail created for Better Search. + * + * @since 2.3.0 + * + * @param string $output Formatted output + * @param array $args Argument list + * @param string $postimage Thumbnail URL + */ + return apply_filters( self::$prefix . '_get_the_post_thumbnail', $output, $args, $postimage ); + } + + /** + * Get an HTML img element + * + * @since 2.3.0 + * + * @param string $attachment_url Image URL. + * @param array $attr Attributes for the image markup. + * @return string HTML img element or empty string on failure. + */ + public static function get_image_html( $attachment_url, $attr = array() ) { + $get_option_callback = self::$prefix . '_get_option'; + + // If there is no url, return. + if ( ! $attachment_url ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison + return ''; + } + + $default_attr = array( + 'src' => $attachment_url, + 'thumb_html' => call_user_func( $get_option_callback, 'thumb_html', 'html' ), + 'thumb_width' => call_user_func( $get_option_callback, 'thumb_width', 150 ), + 'thumb_height' => call_user_func( $get_option_callback, 'thumb_height', 150 ), + ); + + $attr = wp_parse_args( $attr, $default_attr ); + + $hwstring = self::get_image_hwstring( $attr ); + + // Generate 'srcset' and 'sizes' if not already present. + if ( empty( $attr['srcset'] ) ) { + $attachment_id = self::get_attachment_id_from_url( $attachment_url ); + $image_meta = wp_get_attachment_metadata( $attachment_id ); + + if ( is_array( $image_meta ) ) { + $size_array = array( absint( $attr['thumb_width'] ), absint( $attr['thumb_height'] ) ); + $srcset = wp_calculate_image_srcset( $size_array, $attachment_url, $image_meta, $attachment_id ); + $sizes = wp_calculate_image_sizes( $size_array, $attachment_url, $image_meta, $attachment_id ); + + if ( $srcset && ( $sizes || ! empty( $attr['sizes'] ) ) ) { + $attr['srcset'] = $srcset; + + if ( empty( $attr['sizes'] ) ) { + $attr['sizes'] = $sizes; + } + } + } + } + + // Unset attributes we don't want to display. + unset( $attr['thumb_html'] ); + unset( $attr['thumb_width'] ); + unset( $attr['thumb_height'] ); + + /** + * Filters the list of attachment image attributes. + * + * @since 2.3.0 + * + * @param array $attr Attributes for the image markup. + * @param string $attachment_url Image URL. + */ + $attr = apply_filters( self::$prefix . '_get_image_attributes', $attr, $attachment_url ); + $attr = array_map( 'esc_attr', $attr ); + + $html = ' $value ) { + if ( '' === $value ) { + continue; + } + $html .= " $name=" . '"' . $value . '"'; + } + $html .= ' />'; + + /** + * Filters the img tag. + * + * @since 2.3.0 + * + * @param string $html HTML img element or empty string on failure. + * @param string $attachment_url Image URL. + * @param array $attr Attributes for the image markup. + */ + return apply_filters( self::$prefix . '_get_image_html', $html, $attachment_url, $attr ); + } + + + /** + * Retrieve width and height attributes using given width and height values. + * + * @since 2.3.0 + * + * @param array $args Argument array. + * + * @return string Height-width string. + */ + public static function get_image_hwstring( $args = array() ) { + $get_option_callback = self::$prefix . '_get_option'; + + $default_args = array( + 'thumb_html' => call_user_func( $get_option_callback, 'thumb_html', 'html' ), + 'thumb_width' => call_user_func( $get_option_callback, 'thumb_width', 150 ), + 'thumb_height' => call_user_func( $get_option_callback, 'thumb_height', 150 ), + ); + + $args = wp_parse_args( $args, $default_args ); + + if ( 'css' === $args['thumb_html'] ) { + $thumb_html = ' style="max-width:' . $args['thumb_width'] . 'px;max-height:' . $args['thumb_height'] . 'px;" '; + } elseif ( 'html' === $args['thumb_html'] ) { + $thumb_html = ' width="' . $args['thumb_width'] . '" height="' . $args['thumb_height'] . '" '; + } else { + $thumb_html = ''; + } + + /** + * Filters the thumbnail HTML and allows a filter function to add any more HTML if needed. + * + * @since 2.3.0 + * + * @param string $thumb_html Thumbnail HTML. + * @param array $args Argument array. + */ + return apply_filters( self::$prefix . '_thumb_html', $thumb_html, $args ); + } + + + /** + * Get the first child image in the post. + * + * @since 2.3.0 + * + * @param mixed $postid Post ID. + * @param int $thumb_width Thumb width. + * @param int $thumb_height Thumb height. + * @return string Location of thumbnail + */ + public static function get_first_image( $postid, $thumb_width, $thumb_height ) { + $args = array( + 'numberposts' => 1, + 'order' => 'ASC', + 'post_mime_type' => 'image', + 'post_parent' => $postid, + 'post_status' => null, + 'post_type' => 'attachment', + ); + + $attachments = get_children( $args ); + + if ( $attachments ) { + foreach ( $attachments as $attachment ) { + $image_attributes = wp_get_attachment_image_src( $attachment->ID, array( $thumb_width, $thumb_height ) ) ? wp_get_attachment_image_src( $attachment->ID, array( $thumb_width, $thumb_height ) ) : wp_get_attachment_image_src( $attachment->ID, 'full' ); + + /** + * Filters first child attachment from the post. + * + * @since 2.3.0 + * + * @param string $image_attributes[0] URL of the image + * @param int $postid Post ID + * @param int $thumb_width Thumb width + * @param int $thumb_height Thumb height + */ + return apply_filters( self::$prefix . '_get_first_image', $image_attributes[0], $postid, $thumb_width, $thumb_height ); + } + } else { + return ''; + } + } + + + /** + * Function to get the attachment ID from the attachment URL. + * + * @since 2.3.0 + * + * @param string $attachment_url Attachment URL. + * @return int Attachment ID + */ + public static function get_attachment_id_from_url( $attachment_url = '' ) { + + global $wpdb; + $attachment_id = false; + + // If there is no url, return. + if ( ! $attachment_url ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison + return 0; + } + + // Get the upload directory paths. + $upload_dir_paths = wp_upload_dir(); + + // Make sure the upload path base directory exists in the attachment URL, to verify that we're working with a media library image. + if ( false !== strpos( $attachment_url, $upload_dir_paths['baseurl'] ) ) { + + // If this is the URL of an auto-generated thumbnail, get the URL of the original image. + $attachment_url = preg_replace( '/-\d+x\d+(?=\.(jpg|jpeg|png|gif)$)/i', '', $attachment_url ); + + // Remove the upload path base directory from the attachment URL. + $attachment_url = str_replace( $upload_dir_paths['baseurl'] . '/', '', $attachment_url ); + + // Finally, run a custom database query to get the attachment ID from the modified attachment URL. + $attachment_id = $wpdb->get_var( $wpdb->prepare( "SELECT wposts.ID FROM $wpdb->posts wposts, $wpdb->postmeta wpostmeta WHERE wposts.ID = wpostmeta.post_id AND wpostmeta.meta_key = '_wp_attached_file' AND wpostmeta.meta_value = %s AND wposts.post_type = 'attachment'", $attachment_url ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + + } + + /** + * Filter the attachment ID from the attachment URL. + * + * @since 2.3.0 + * + * @param int $attachment_id Attachment ID + * @param string $attachment_url Attachment URL + */ + return apply_filters( self::$prefix . '_get_attachment_id_from_url', $attachment_id, $attachment_url ); + } + + + /** + * Function to get the correct height and width of the thumbnail. + * + * @since 2.3.0 + * + * @param string $size Image size. + * @return array Width and height. If no width and height is found, then 150 is returned for each. + */ + public static function get_thumb_size( $size = 'thumbnail' ) { + + // Get thumbnail size. + $thumb_size_array = self::get_all_image_sizes( $size ); + + if ( isset( $thumb_size_array['width'] ) ) { + $thumb_width = $thumb_size_array['width']; + $thumb_height = $thumb_size_array['height']; + } + + if ( isset( $thumb_width ) && isset( $thumb_height ) ) { + $thumb_size = array( $thumb_width, $thumb_height ); + } else { + $thumb_size = array( 150, 150 ); + } + + /** + * Filter array of thumbnail size. + * + * @since 2.3.0 + * + * @param array $thumb_size Array with width and height of thumbnail + */ + return apply_filters( self::$prefix . '_get_thumb_size', $thumb_size ); + } + + + /** + * Get all image sizes. + * + * @since 2.3.0 + * + * @param string|int[] $size Image size. + * @return array|bool If a single size is specified, then the array with width, height and crop status + * or false if size is not found; + * If no size is specified then an Associative array of the registered image sub-sizes. + */ + public static function get_all_image_sizes( $size = '' ) { + + if ( is_array( $size ) ) { + $size = self::get_appropriate_image_size( $size[0], $size[1] ); + } + + $sizes = wp_get_registered_image_subsizes(); + + /* Get only 1 size if found */ + if ( $size ) { + if ( isset( $sizes[ $size ] ) ) { + return $sizes[ $size ]; + } else { + return false; + } + } + + /** + * Filters array of image sizes. + * + * @since 2.3.0 + * + * @param array $sizes Image sizes + */ + return apply_filters( self::$prefix . '_get_all_image_sizes', $sizes ); + } + + /** + * Get the most appropriate image size based on the given thumbnail width and height. + * + * @since 2.3.0 + * + * @param int $thumb_width Thumbnail width. + * @param int $thumb_height Thumbnail height. + * + * @return string|bool Image size name if found, false otherwise. + */ + public static function get_appropriate_image_size( $thumb_width, $thumb_height ) { + $sizes = wp_get_registered_image_subsizes(); + + $closest_size = false; + $closest_distance = PHP_INT_MAX; + + foreach ( $sizes as $size_name => $size_info ) { + $size_width = $size_info['width']; + $size_height = $size_info['height']; + $distance = sqrt( pow( $thumb_width - $size_width, 2 ) + pow( $thumb_height - $size_height, 2 ) ); + + if ( $distance < $closest_distance ) { + $closest_distance = $distance; + $closest_size = $size_name; + } + } + + return $closest_size; + } +} diff --git a/includes/frontend/class-related.php b/includes/frontend/class-related.php new file mode 100644 index 0000000..b77fe21 --- /dev/null +++ b/includes/frontend/class-related.php @@ -0,0 +1,150 @@ + 5, + 'post' => get_post(), + 'exclude' => array(), + 'show_thumb' => true, + 'show_date' => true, + 'title' => '

    ' . __( 'Related Articles', 'knowledgebase' ) . '

    ', + 'thumb_size' => 'thumbnail', + ); + + // Parse incomming $args into an array and merge it with $defaults. + $args = wp_parse_args( $args, $defaults ); + + // Assign post to a separate variable for easy processing. + $post = $args['post']; + + $exclude = array( $post->ID ); + if ( ! empty( $args['exclude'] ) ) { + $exclude = array_merge( $exclude, wp_parse_id_list( $args['exclude'] ) ); + } + + $categories = get_the_terms( $post, 'wzkb_category' ); + $tags = get_the_terms( $post, 'wzkb_tag' ); + + if ( empty( $categories ) ) { + $categories = array(); + } + if ( empty( $tags ) ) { + $tags = array(); + } + + $categories_list = wp_list_pluck( $categories, 'slug' ); + $tags_list = wp_list_pluck( $tags, 'slug' ); + + $related_args = array( + 'ignore_sticky_posts' => true, + 'no_found_rows' => true, + 'post_type' => 'wz_knowledgebase', + 'posts_per_page' => $args['numberposts'], + 'post_status' => 'publish', + 'post__not_in' => $exclude, + 'orderby' => 'rand', + 'tax_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query + 'relation' => 'OR', + array( + 'taxonomy' => 'wzkb_category', + 'field' => 'slug', + 'terms' => $categories_list, + ), + array( + 'taxonomy' => 'wzkb_tag', + 'field' => 'slug', + 'terms' => $tags_list, + ), + ), + ); + + /** + * Filters the related articles arguments before it is passed to WP_Query. + * + * @since 2.1.0 + * + * @param array $related_args WP_Query arguments. + * @param array $args Parameters passed to function and merged with defaults. + */ + $related_args = apply_filters( 'wzkb_related_articles_query_args', $related_args, $args ); + + $query = new \WP_Query( $related_args ); + + $output = ''; + + if ( $query->have_posts() ) { + $output .= ''; + } + + return $output; + } +} diff --git a/includes/frontend/class-search.php b/includes/frontend/class-search.php new file mode 100644 index 0000000..0067225 --- /dev/null +++ b/includes/frontend/class-search.php @@ -0,0 +1,62 @@ +' + . '' + . '' + . '' + . ''; + + /** + * Filter the HTML output of the search form. + * + * @since 1.1.0 + * + * @param string|null $form The search form HTML output. + */ + $result = apply_filters( 'wzkb_get_search_form', $form ); + + if ( null === $result ) { + $result = $form; + } + + return trim( $result ); + } +} diff --git a/includes/frontend/class-shortcodes.php b/includes/frontend/class-shortcodes.php new file mode 100644 index 0000000..03c63c1 --- /dev/null +++ b/includes/frontend/class-shortcodes.php @@ -0,0 +1,221 @@ + false, + 'is_shortcode' => 1, + ), + $atts, + 'knowledgebase' + ); + + $output = wzkb_knowledge( $atts ); + + /** + * Filters knowledgebase shortcode. + * + * @since 1.0.0 + * + * @param string $output Formatted shortcode output + * @param array $att Shortcode attributes array + * @param string $content Content to wrap in the Shortcode + */ + return apply_filters( 'wzkb_shortcode', $output, $atts, $content ); + } + + /** + * Create the shortcode to display the search form using [kbsearch]. + * + * @since 2.3.0 + * + * @param array $atts Shortcode attributes array. + * @param string $content Content to wrap in the Shortcode. + * @return string $output Formatted shortcode output + */ + public static function search_form( $atts, $content = null ) { + + $atts = shortcode_atts( + array( + 'echo' => false, + ), + $atts, + 'kbsearch' + ); + + $output = wzkb_get_search_form(); + + /** + * Filters knowledge base search form shortcode. + * + * @since 1.2.0 + * + * @param string $output Formatted shortcode output + * @param array $att Shortcode attributes array + * @param string $content Content to wrap in the Shortcode + */ + return apply_filters( 'wzkb_shortcode_search', $output, $atts, $content ); + } + + + /** + * Create the shortcode to display the breadcrumb using [kbbreadcrumb]. + * + * @since 2.3.0 + * + * @param array $atts Shortcode attributes array. + * @param string $content Content to wrap in the Shortcode. + * @return string $output Formatted shortcode output + */ + public static function breadcrumb( $atts, $content = null ) { + + $atts = shortcode_atts( + array( + 'separator' => ' » ', // Separator. + ), + $atts, + 'kbbreadcrumb' + ); + + $output = wzkb_get_breadcrumb( $atts ); + + /** + * Filters knowledge base breadcrumb shortcode. + * + * @since 1.6.0 + * + * @param string $output Formatted shortcode output + * @param array $att Shortcode attributes array + * @param string $content Content to wrap in the Shortcode + */ + return apply_filters( 'wzkb_shortcode_breadcrumb', $output, $atts, $content ); + } + + /** + * Create the shortcode to display alerts using [kbalert]. + * + * @since 2.3.0 + * + * @param array $atts Shortcode attributes array. + * @param string $content Content to wrap in the Shortcode. + * @return string $output Formatted shortcode output + */ + public static function alert( $atts, $content = null ) { + + if ( wzkb_get_option( 'include_styles' ) ) { + wp_enqueue_style( 'wz-knowledgebase-styles' ); + } + + $atts = shortcode_atts( + array( + 'type' => 'primary', + 'class' => 'alert', + 'text' => '', + ), + $atts, + 'kbalert' + ); + + $output = wzkb_get_alert( $atts, $content ); + + /** + * Filters knowledge base breadcrumb shortcode. + * + * @since 1.7.0 + * + * @param string $output Formatted shortcode output + * @param array $att Shortcode attributes array + * @param string $content Content to wrap in the Shortcode + */ + return apply_filters( 'wzkb_shortcode_alert', $output, $atts, $content ); + } + + + /** + * Create the shortcode to display related articles using [kb_related_articles]. + * + * @since 2.3.0 + * + * @param array $atts Shortcode attributes array. + * @param string $content Content to wrap in the Shortcode. + * @return string $output Formatted shortcode output + */ + public static function related_articles( $atts, $content = null ) { + + if ( wzkb_get_option( 'include_styles' ) ) { + wp_enqueue_style( 'wz-knowledgebase-styles' ); + } + + $atts = shortcode_atts( + array( + 'numberposts' => 5, + 'echo' => false, + 'post' => get_post(), + 'exclude' => array(), + 'show_thumb' => true, + 'show_date' => true, + 'title' => '

    ' . __( 'Related Articles', 'knowledgebase' ) . '

    ', + 'thumb_size' => 'thumbnail', + ), + $atts, + 'kb_related_articles' + ); + + $output = wzkb_related_articles( $atts ); + + /** + * Filters knowledge base breadcrumb shortcode. + * + * @since 2.2.2 + * + * @param string $output Formatted shortcode output + * @param array $att Shortcode attributes array + * @param string $content Content to wrap in the Shortcode + */ + return apply_filters( 'wzkb_shortcode_related_articles', $output, $atts, $content ); + } +} diff --git a/includes/frontend/class-styles-handler.php b/includes/frontend/class-styles-handler.php new file mode 100644 index 0000000..2fc7a7f --- /dev/null +++ b/includes/frontend/class-styles-handler.php @@ -0,0 +1,74 @@ + $callback ) { add_filter( "{$type}_template_hierarchy", array( $this, $callback ) ); } + + add_filter( 'pre_get_posts', array( $this, 'posts_per_search_page' ) ); + add_filter( 'document_title_parts', array( $this, 'update_title' ), 99999 ); + add_action( 'widgets_init', array( $this, 'register_sidebars' ), 11 ); + } + + /** + * Replace the archive temlate for the knowledge base. Filters template_include. + * + * To further customize these archive views, you may create a + * new template file for each one in your theme's folder: + * archive-wz_knowledgebase.php (Main KB archives), wzkb-category.php (Category/Section archives), + * wzkb-search.php (Search results page) or taxonomy-wzkb_tag.php (Tag archives) + * + * @since 2.3.0 + * + * @param string $template Default Archive Template location. + * @return string Modified Archive Template location + */ + public function archive_template( $template ) { + if ( function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ) { + return $template; + } + + $template_name = null; + + if ( is_singular( 'wz_knowledgebase' ) ) { + $template_name = 'single-wz_knowledgebase.php'; + } elseif ( is_post_type_archive( 'wz_knowledgebase' ) ) { + $template_name = is_search() ? 'wzkb-search.php' : 'archive-wz_knowledgebase.php'; + } elseif ( is_tax( 'wzkb_category' ) && ! is_search() ) { + $template_name = 'taxonomy-wzkb_category.php'; + } + + if ( $template_name ) { + $new_template = locate_template( array( $template_name ) ); + if ( $new_template ) { + return $new_template; + } + + $new_template = WP_CONTENT_DIR . '/knowledgebase/templates/' . $template_name; + if ( file_exists( $new_template ) ) { + return $new_template; + } + $new_template = __DIR__ . '/templates/' . $template_name; + if ( file_exists( $new_template ) ) { + return $new_template; + } + } + + return $template; } /** @@ -213,6 +264,60 @@ public static function get_template_content( $template ) { include $template; return ob_get_clean(); } -} -new Template_Handler(); + /** + * For knowledge base search results, set posts_per_page 10. + * + * @since 2.3.0 + * + * @param \WP_Query $query The search query object. + * @return \WP_Query $query Updated search query object + */ + public function posts_per_search_page( $query ) { + + if ( ! is_admin() && $query->is_search() && isset( $query->query_vars['post_type'] ) && 'wz_knowledgebase' === $query->query_vars['post_type'] ) { + $query->set( 'posts_per_page', 12 ); + $query->set( 'post_type', 'wz_knowledgebase' ); + } + + return $query; + } + + + /** + * Update the title on WZKB archive. + * + * @since 2.3.0 + * + * @param array $title Title of the page. + * @return array Updated title + */ + public function update_title( $title ) { + + if ( is_post_type_archive( 'wz_knowledgebase' ) && ! is_search() ) { + $title['title'] = wzkb_get_option( 'kb_title' ); + } + + return $title; + } + + /** + * Register the WZ Knowledge Base sidebars. + * + * @since 2.3.0 + */ + public function register_sidebars() { + /* Register the 'wzkb-primary' sidebar. */ + register_sidebar( + array( + 'id' => 'wzkb-primary', + 'name' => __( 'WZ Knowledge Base Sidebar', 'knowledgebase' ), + 'description' => __( 'Displays on WZ Knowledge Base templates displayed by the plugin', 'knowledgebase' ), + 'before_widget' => '
    ', + 'after_widget' => '
    ', + 'before_title' => '

    ', + 'after_title' => '

    ', + ) + ); + } +} diff --git a/includes/admin/index.php b/includes/frontend/css/index.php similarity index 100% rename from includes/admin/index.php rename to includes/frontend/css/index.php diff --git a/includes/public/css/wzkb-styles-rtl.css b/includes/frontend/css/wzkb-styles-rtl.css similarity index 100% rename from includes/public/css/wzkb-styles-rtl.css rename to includes/frontend/css/wzkb-styles-rtl.css diff --git a/includes/public/css/wzkb-styles-rtl.min.css b/includes/frontend/css/wzkb-styles-rtl.min.css similarity index 100% rename from includes/public/css/wzkb-styles-rtl.min.css rename to includes/frontend/css/wzkb-styles-rtl.min.css diff --git a/includes/public/css/wzkb-styles.css b/includes/frontend/css/wzkb-styles.css similarity index 100% rename from includes/public/css/wzkb-styles.css rename to includes/frontend/css/wzkb-styles.css diff --git a/includes/public/css/wzkb-styles.min.css b/includes/frontend/css/wzkb-styles.min.css similarity index 100% rename from includes/public/css/wzkb-styles.min.css rename to includes/frontend/css/wzkb-styles.min.css diff --git a/includes/public/images/default-thumb.png b/includes/frontend/images/default-thumb.png similarity index 100% rename from includes/public/images/default-thumb.png rename to includes/frontend/images/default-thumb.png diff --git a/includes/public/images/search-icon.png b/includes/frontend/images/search-icon.png similarity index 100% rename from includes/public/images/search-icon.png rename to includes/frontend/images/search-icon.png diff --git a/includes/public/templates/archive-wz_knowledgebase.html b/includes/frontend/templates/archive-wz_knowledgebase.html similarity index 100% rename from includes/public/templates/archive-wz_knowledgebase.html rename to includes/frontend/templates/archive-wz_knowledgebase.html diff --git a/includes/public/templates/archive-wz_knowledgebase.php b/includes/frontend/templates/archive-wz_knowledgebase.php similarity index 90% rename from includes/public/templates/archive-wz_knowledgebase.php rename to includes/frontend/templates/archive-wz_knowledgebase.php index 81a40b2..a9183d4 100644 --- a/includes/public/templates/archive-wz_knowledgebase.php +++ b/includes/frontend/templates/archive-wz_knowledgebase.php @@ -7,23 +7,20 @@ * If you'd like to further customize these archive views, you may create a * archive-wz_knowledgebase.php file in your theme's folder * - * @link https://webberzone.com - * @since 1.9.0 - * - * @package WZKB + * @package WebberZone\Knowledge_Base */ /* This plugin uses the Archive file of TwentySeventeen theme as an example */ get_header(); if ( wzkb_get_option( 'include_styles' ) ) { - wp_enqueue_style( 'wzkb_styles' ); + wp_enqueue_style( 'wz-knowledgebase-styles' ); } ?>
    - +