Skip to content

How to create a WordPress Plugin using the WPPB Boilerplate – Including a beautyful Settings Page

First of all head over to the generator and type in your data. Unfortunately they don’t provideany privacy information so enter your oersonal data on your own risk! The generator changes a lot – not only filenames but also all those classes and names within the code itself. So I would personally advise the generator but make sure you are ok with the conditions.

1. Alternative: The GIT-Repository

of yourse you can also use Devin Visons original repository files but they still need all the name changes otherwise ayour plugin will be branded as the „WordPress Plugin Boilerplate„.

2. Alternative: Use the Demo Setup of the Boilerplate

There is another alternative which is the demo setup Devin Vison provides. This setup has another branding „WPPB Demo Plugin“ which still needs adaption for your individual usecase. but there is ione major advatage: This version has the actual settings page!

Anyways the best option is to generate the plugin with your data with the generator and then follow the steps below to enhance the final plugin with a settings page. This is what this tutorial is all about 🙂

Integrating the Settings Page in the WPPB Boilerplate Generator Output

Unfortunately the regular generator output does NOT include a settings page. But you can rather easily accomplish this by following these steps:

1. Enhance the basic plugin class:

First of all you need to enhance your basic plugin class to tell it to hook some actions in the backend area. Open this file:

  • includes/class-enym.php
    (where „enym“ is your full plugins name).

Find the section with the „define_admin_hooks“ function that sets the actions for the backend. Add the following highlighted lines so it looks like this code block below. Make sure to replace „Enym“ with your full plugins name!

	private function define_admin_hooks() {

		$plugin_admin = new Enym_Admin( $this->get_plugin_name(), $this->get_version() );
    
    $this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_styles' );
		$this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_scripts' );
    
    //enym settings
    $plugin_settings = new Enym_Admin_Settings( $this->get_plugin_name(), $this->get_version() );
    $this->loader->add_action( 'admin_menu', $plugin_settings, 'setup_plugin_options_menu' );
		$this->loader->add_action( 'admin_init', $plugin_settings, 'initialize_display_options' );
		$this->loader->add_action( 'admin_init', $plugin_settings, 'initialize_social_options' );
		$this->loader->add_action( 'admin_init', $plugin_settings, 'initialize_input_examples' );

	}

2. Create the Actual Settings file that will be loaded inside the Backend

Currently our boilerplate plugin is missing the actual settings file. Simply create one, put the code below in it and place it in the /admin folder. This file should be named like this:

  • class-enym-admin-settings.php
    (where „enym“ is your full plugins name).

IMPORTANT: Make sure that you rename the class properly: „Enym_“ should be your full uppercase plugin name. You can even dig further and also replace the other „enym“ occurences in the classes and metas etc. You can also load the original file from this link below and replace the parts there:

<?php

/**
 * The settings of the plugin.
 *
 * @link       https://www.enym.com
 * @since      1.0.0
 *
 * @package    Enym
 * @subpackage Enym/admin
 */

/**
 * Class WordPress_Plugin_Template_Settings
 *
 */
class Enym_Admin_Settings {

	/**
	 * The ID of this plugin.
	 *
	 * @since    1.0.0
	 * @access   private
	 * @var      string    $plugin_name    The ID of this plugin.
	 */
	private $plugin_name;

	/**
	 * The version of this plugin.
	 *
	 * @since    1.0.0
	 * @access   private
	 * @var      string    $version    The current version of this plugin.
	 */
	private $version;

	/**
	 * Initialize the class and set its properties.
	 *
	 * @since    1.0.0
	 * @param      string    $plugin_name       The name of this plugin.
	 * @param      string    $version    The version of this plugin.
	 */
	public function __construct( $plugin_name, $version ) {

		$this->plugin_name = $plugin_name;
		$this->version = $version;

	}

	/**
	 * This function introduces the theme options into the 'Appearance' menu and into a top-level
	 * 'WPPB Demo' menu.
	 */
	public function setup_plugin_options_menu() {

		//Add the menu to the Plugins set of menu items
		add_plugins_page(
			'WPPB Demo Options', 					// The title to be displayed in the browser window for this page.
			'WPPB Demo Options',					// The text to be displayed for this menu item
			'manage_options',					// Which type of users can see this menu item
			'enym_options',			// The unique ID - that is, the slug - for this menu item
			array( $this, 'render_settings_page_content')				// The name of the function to call when rendering this menu's page
		);

	}

	/**
	 * Provides default values for the Display Options.
	 *
	 * @return array
	 */
	public function default_display_options() {

		$defaults = array(
			'show_header'		=>	'',
			'show_content'		=>	'',
			'show_footer'		=>	'',
		);

		return $defaults;

	}

	/**
	 * Provide default values for the Social Options.
	 *
	 * @return array
	 */
	public function default_social_options() {

		$defaults = array(
			'twitter'		=>	'twitter',
			'facebook'		=>	'',
			'googleplus'	=>	'',
		);

		return  $defaults;

	}

	/**
	 * Provides default values for the Input Options.
	 *
	 * @return array
	 */
	public function default_input_options() {

		$defaults = array(
			'input_example'		=>	'default input example',
			'textarea_example'	=>	'',
			'checkbox_example'	=>	'',
			'radio_example'		=>	'2',
			'time_options'		=>	'default'
		);

		return $defaults;

	}

	/**
	 * Renders a simple page to display for the theme menu defined above.
	 */
	public function render_settings_page_content( $active_tab = '' ) {
		?>
		<!-- Create a header in the default WordPress 'wrap' container -->
		<div class="wrap">

			<h2><?php _e( 'WPPB Demo Options', 'enym-plugin' ); ?></h2>
			<?php settings_errors(); ?>

			<?php if( isset( $_GET[ 'tab' ] ) ) {
				$active_tab = $_GET[ 'tab' ];
			} else if( $active_tab == 'social_options' ) {
				$active_tab = 'social_options';
			} else if( $active_tab == 'input_examples' ) {
				$active_tab = 'input_examples';
			} else {
				$active_tab = 'display_options';
			} // end if/else ?>

			<h2 class="nav-tab-wrapper">
				<a href="?page=enym_options&tab=display_options" class="nav-tab <?php echo $active_tab == 'display_options' ? 'nav-tab-active' : ''; ?>"><?php _e( 'Display Options', 'enym-plugin' ); ?></a>
				<a href="?page=enym_options&tab=social_options" class="nav-tab <?php echo $active_tab == 'social_options' ? 'nav-tab-active' : ''; ?>"><?php _e( 'Social Options', 'enym-plugin' ); ?></a>
				<a href="?page=enym_options&tab=input_examples" class="nav-tab <?php echo $active_tab == 'input_examples' ? 'nav-tab-active' : ''; ?>"><?php _e( 'Input Examples', 'enym-plugin' ); ?></a>
			</h2>

			<form method="post" action="options.php">
				<?php

				if( $active_tab == 'display_options' ) {

					settings_fields( 'enym_display_options' );
					do_settings_sections( 'enym_display_options' );

				} elseif( $active_tab == 'social_options' ) {

					settings_fields( 'enym_social_options' );
					do_settings_sections( 'enym_social_options' );

				} else {

					settings_fields( 'enym_input_examples' );
					do_settings_sections( 'enym_input_examples' );

				} // end if/else

				submit_button();

				?>
			</form>

		</div><!-- /.wrap -->
	<?php
	}


	/**
	 * This function provides a simple description for the General Options page.
	 *
	 * It's called from the 'wppb-demo_initialize_theme_options' function by being passed as a parameter
	 * in the add_settings_section function.
	 */
	public function general_options_callback() {
		$options = get_option('enym_display_options');
		var_dump($options);
		echo '<p>' . __( 'Select which areas of content you wish to display.', 'enym-plugin' ) . '</p>';
	} // end general_options_callback

	/**
	 * This function provides a simple description for the Social Options page.
	 *
	 * It's called from the 'wppb-demo_theme_initialize_social_options' function by being passed as a parameter
	 * in the add_settings_section function.
	 */
	public function social_options_callback() {
		$options = get_option('enym_social_options');
		var_dump($options);
		echo '<p>' . __( 'Provide the URL to the social networks you\'d like to display.', 'enym-plugin' ) . '</p>';
	} // end general_options_callback

	/**
	 * This function provides a simple description for the Input Examples page.
	 *
	 * It's called from the 'wppb-demo_theme_initialize_input_examples_options' function by being passed as a parameter
	 * in the add_settings_section function.
	 */
	public function input_examples_callback() {
		$options = get_option('enym_input_examples');
		var_dump($options);
		echo '<p>' . __( 'Provides examples of the five basic element types.', 'enym-plugin' ) . '</p>';
	} // end general_options_callback


	/**
	 * Initializes the theme's display options page by registering the Sections,
	 * Fields, and Settings.
	 *
	 * This function is registered with the 'admin_init' hook.
	 */
	public function initialize_display_options() {

		// If the theme options don't exist, create them.
		if( false == get_option( 'enym_display_options' ) ) {
			$default_array = $this->default_display_options();
			add_option( 'enym_display_options', $default_array );
		}


		add_settings_section(
			'general_settings_section',			            // ID used to identify this section and with which to register options
			__( 'Display Options', 'enym-plugin' ),		        // Title to be displayed on the administration page
			array( $this, 'general_options_callback'),	    // Callback used to render the description of the section
			'enym_display_options'		                // Page on which to add this section of options
		);

		// Next, we'll introduce the fields for toggling the visibility of content elements.
		add_settings_field(
			'show_header',						        // ID used to identify the field throughout the theme
			__( 'Header', 'enym-plugin' ),					// The label to the left of the option interface element
			array( $this, 'toggle_header_callback'),	// The name of the function responsible for rendering the option interface
			'enym_display_options',	            // The page on which this option will be displayed
			'general_settings_section',			        // The name of the section to which this field belongs
			array(								        // The array of arguments to pass to the callback. In this case, just a description.
				__( 'Activate this setting to display the header.', 'enym-plugin' ),
			)
		);

		add_settings_field(
			'show_content',
			__( 'Content', 'enym-plugin' ),
			array( $this, 'toggle_content_callback'),
			'enym_display_options',
			'general_settings_section',
			array(
				__( 'Activate this setting to display the content.', 'enym-plugin' ),
			)
		);

		add_settings_field(
			'show_footer',
			__( 'Footer', 'enym-plugin' ),
			array( $this, 'toggle_footer_callback'),
			'enym_display_options',
			'general_settings_section',
			array(
				__( 'Activate this setting to display the footer.', 'enym-plugin' ),
			)
		);

		// Finally, we register the fields with WordPress
		register_setting(
			'enym_display_options',
			'enym_display_options'
		);

	} // end wppb-demo_initialize_theme_options


	/**
	 * Initializes the theme's social options by registering the Sections,
	 * Fields, and Settings.
	 *
	 * This function is registered with the 'admin_init' hook.
	 */
	public function initialize_social_options() {
		delete_option('enym_social_options');
		if( false == get_option( 'enym_social_options' ) ) {
			$default_array = $this->default_social_options();
			update_option( 'enym_social_options', $default_array );
		} // end if

		add_settings_section(
			'social_settings_section',			// ID used to identify this section and with which to register options
			__( 'Social Options', 'enym-plugin' ),		// Title to be displayed on the administration page
			array( $this, 'social_options_callback'),	// Callback used to render the description of the section
			'enym_social_options'		// Page on which to add this section of options
		);

		add_settings_field(
			'twitter',
			'Twitter',
			array( $this, 'twitter_callback'),
			'enym_social_options',
			'social_settings_section'
		);

		add_settings_field(
			'facebook',
			'Facebook',
			array( $this, 'facebook_callback'),
			'enym_social_options',
			'social_settings_section'
		);

		add_settings_field(
			'googleplus',
			'Google+',
			array( $this, 'googleplus_callback'),
			'enym_social_options',
			'social_settings_section'
		);

		register_setting(
			'enym_social_options',
			'enym_social_options',
			array( $this, 'sanitize_social_options')
		);

	}


	/**
	 * Initializes the theme's input example by registering the Sections,
	 * Fields, and Settings. This particular group of options is used to demonstration
	 * validation and sanitization.
	 *
	 * This function is registered with the 'admin_init' hook.
	 */
	public function initialize_input_examples() {
		//delete_option('enym_input_examples');
		if( false == get_option( 'enym_input_examples' ) ) {
			$default_array = $this->default_input_options();
			update_option( 'enym_input_examples', $default_array );
		} // end if

		add_settings_section(
			'input_examples_section',
			__( 'Input Examples', 'enym-plugin' ),
			array( $this, 'input_examples_callback'),
			'enym_input_examples'
		);

		add_settings_field(
			'Input Element',
			__( 'Input Element', 'enym-plugin' ),
			array( $this, 'input_element_callback'),
			'enym_input_examples',
			'input_examples_section'
		);

		add_settings_field(
			'Textarea Element',
			__( 'Textarea Element', 'enym-plugin' ),
			array( $this, 'textarea_element_callback'),
			'enym_input_examples',
			'input_examples_section'
		);

		add_settings_field(
			'Checkbox Element',
			__( 'Checkbox Element', 'enym-plugin' ),
			array( $this, 'checkbox_element_callback'),
			'enym_input_examples',
			'input_examples_section'
		);

		add_settings_field(
			'Radio Button Elements',
			__( 'Radio Button Elements', 'enym-plugin' ),
			array( $this, 'radio_element_callback'),
			'enym_input_examples',
			'input_examples_section'
		);

		add_settings_field(
			'Select Element',
			__( 'Select Element', 'enym-plugin' ),
			array( $this, 'select_element_callback'),
			'enym_input_examples',
			'input_examples_section'
		);

		register_setting(
			'enym_input_examples',
			'enym_input_examples',
			array( $this, 'validate_input_examples')
		);

	}

	/**
	 * This function renders the interface elements for toggling the visibility of the header element.
	 *
	 * It accepts an array or arguments and expects the first element in the array to be the description
	 * to be displayed next to the checkbox.
	 */
	public function toggle_header_callback($args) {

		// First, we read the options collection
		$options = get_option('enym_display_options');

		// Next, we update the name attribute to access this element's ID in the context of the display options array
		// We also access the show_header element of the options collection in the call to the checked() helper function
		$html = '<input type="checkbox" id="show_header" name="enym_display_options[show_header]" value="1" ' . checked( 1, isset( $options['show_header'] ) ? $options['show_header'] : 0, false ) . '/>';

		// Here, we'll take the first argument of the array and add it to a label next to the checkbox
		$html .= '<label for="show_header"> '  . $args[0] . '</label>';

		echo $html;

	} // end toggle_header_callback

	public function toggle_content_callback($args) {

		$options = get_option('enym_display_options');

		$html = '<input type="checkbox" id="show_content" name="enym_display_options[show_content]" value="1" ' . checked( 1, isset( $options['show_content'] ) ? $options['show_content'] : 0, false ) . '/>';
		$html .= '<label for="show_content"> '  . $args[0] . '</label>';

		echo $html;

	} // end toggle_content_callback

	public function toggle_footer_callback($args) {

		$options = get_option('enym_display_options');

		$html = '<input type="checkbox" id="show_footer" name="enym_display_options[show_footer]" value="1" ' . checked( 1, isset( $options['show_footer'] ) ? $options['show_footer'] : 0, false ) . '/>';
		$html .= '<label for="show_footer"> '  . $args[0] . '</label>';

		echo $html;

	} // end toggle_footer_callback

	public function twitter_callback() {

		// First, we read the social options collection
		$options = get_option( 'enym_social_options' );

		// Next, we need to make sure the element is defined in the options. If not, we'll set an empty string.
		$url = '';
		if( isset( $options['twitter'] ) ) {
			$url = esc_url( $options['twitter'] );
		} // end if

		// Render the output
		echo '<input type="text" id="twitter" name="enym_social_options[twitter]" value="' . $url . '" />';

	} // end twitter_callback

	public function facebook_callback() {

		$options = get_option( 'enym_social_options' );

		$url = '';
		if( isset( $options['facebook'] ) ) {
			$url = esc_url( $options['facebook'] );
		} // end if

		// Render the output
		echo '<input type="text" id="facebook" name="enym_social_options[facebook]" value="' . $url . '" />';

	} // end facebook_callback

	public function googleplus_callback() {

		$options = get_option( 'enym_social_options' );

		$url = '';
		if( isset( $options['googleplus'] ) ) {
			$url = esc_url( $options['googleplus'] );
		} // end if

		// Render the output
		echo '<input type="text" id="googleplus" name="enym_social_options[googleplus]" value="' . $url . '" />';

	} // end googleplus_callback

	public function input_element_callback() {

		$options = get_option( 'enym_input_examples' );

		// Render the output
		echo '<input type="text" id="input_example" name="enym_input_examples[input_example]" value="' . $options['input_example'] . '" />';

	} // end input_element_callback

	public function textarea_element_callback() {

		$options = get_option( 'enym_input_examples' );

		// Render the output
		echo '<textarea id="textarea_example" name="enym_input_examples[textarea_example]" rows="5" cols="50">' . $options['textarea_example'] . '</textarea>';

	} // end textarea_element_callback

	public function checkbox_element_callback() {

		$options = get_option( 'enym_input_examples' );

		$html = '<input type="checkbox" id="checkbox_example" name="enym_input_examples[checkbox_example]" value="1"' . checked( 1, $options['checkbox_example'], false ) . '/>';
		$html .= ' ';
		$html .= '<label for="checkbox_example">This is an example of a checkbox</label>';

		echo $html;

	} // end checkbox_element_callback

	public function radio_element_callback() {

		$options = get_option( 'enym_input_examples' );

		$html = '<input type="radio" id="radio_example_one" name="enym_input_examples[radio_example]" value="1"' . checked( 1, $options['radio_example'], false ) . '/>';
		$html .= ' ';
		$html .= '<label for="radio_example_one">Option One</label>';
		$html .= ' ';
		$html .= '<input type="radio" id="radio_example_two" name="enym_input_examples[radio_example]" value="2"' . checked( 2, $options['radio_example'], false ) . '/>';
		$html .= ' ';
		$html .= '<label for="radio_example_two">Option Two</label>';

		echo $html;

	} // end radio_element_callback

	public function select_element_callback() {

		$options = get_option( 'enym_input_examples' );

		$html = '<select id="time_options" name="enym_input_examples[time_options]">';
		$html .= '<option value="default">' . __( 'Select a time option...', 'enym-plugin' ) . '</option>';
		$html .= '<option value="never"' . selected( $options['time_options'], 'never', false) . '>' . __( 'Never', 'enym-plugin' ) . '</option>';
		$html .= '<option value="sometimes"' . selected( $options['time_options'], 'sometimes', false) . '>' . __( 'Sometimes', 'enym-plugin' ) . '</option>';
		$html .= '<option value="always"' . selected( $options['time_options'], 'always', false) . '>' . __( 'Always', 'enym-plugin' ) . '</option>';	$html .= '</select>';

		echo $html;

	} // end select_element_callback


	/**
	 * Sanitization callback for the social options. Since each of the social options are text inputs,
	 * this function loops through the incoming option and strips all tags and slashes from the value
	 * before serializing it.
	 *
	 * @params	$input	The unsanitized collection of options.
	 *
	 * @returns			The collection of sanitized values.
	 */
	public function sanitize_social_options( $input ) {

		// Define the array for the updated options
		$output = array();

		// Loop through each of the options sanitizing the data
		foreach( $input as $key => $val ) {

			if( isset ( $input[$key] ) ) {
				$output[$key] = esc_url_raw( strip_tags( stripslashes( $input[$key] ) ) );
			} // end if

		} // end foreach

		// Return the new collection
		return apply_filters( 'sanitize_social_options', $output, $input );

	} // end sanitize_social_options

	public function validate_input_examples( $input ) {

		// Create our array for storing the validated options
		$output = array();

		// Loop through each of the incoming options
		foreach( $input as $key => $value ) {

			// Check to see if the current option has a value. If so, process it.
			if( isset( $input[$key] ) ) {

				// Strip all HTML and PHP tags and properly handle quoted strings
				$output[$key] = strip_tags( stripslashes( $input[ $key ] ) );

			} // end if

		} // end foreach

		// Return the array processing any additional functions filtered by this action
		return apply_filters( 'validate_input_examples', $output, $input );

	} // end validate_input_examples


}

2. Load our new settings file as a dependency in the backend.

Now open up the following file because we need to set up our dependencies properly to load that just created new file that makes up our actual backend settings page. You have to make changes in two areas of the following file.

  • admin/class-enym-admin.php
    (where „enym“ is your full plugins name).

First define a „load_dependencies“ function that will load our file with our backend settings. The following code can simply be added between one of the the public function definitions (for instance right after the „public function __construct“ block and its closing bracket). Be sure to use the proper filename in line 18!

  /**
	 * Load the required dependencies for the Admin facing functionality.
	 *
	 * Include the following files that make up the plugin:
	 *
	 * - Wppb_Demo_Plugin_Admin_Settings. Registers the admin settings and page.
	 *
	 *
	 * @since    1.0.0
	 * @access   private
	 */
	private function load_dependencies() {
		/**
		 * The class responsible for orchestrating the actions and filters of the
		 * core plugin.
		 */
     
		require_once plugin_dir_path( dirname( __FILE__ ) ) .  'admin/class-enym-admin-settings.php';
 
	}

Then we also have to fire that new „load_dependency“ funtion. Add the following highlighted line to the constructor class:

	public function __construct( $plugin_name, $version ) {

		$this->plugin_name = $plugin_name;
		$this->version = $version;
    
    $this->load_dependencies();

	}

Now you have created your settings page for the WPPB boilerplate plugin. I don’t know why it is not included in the generated oiutput but it is a very useful ressource. The example file includes many valuable examples and a pretty straightrforward designed settings page with several tabs.

Make sure to move it to a proper place (currently it appears below the „plugins“ menu though it belongs unsder „settings“).

22.2.2022: Update from Joreon

About moving the settings page to a different page. This might help for other readers. I changed the add_plugins_page function to the add_menu_page. This way the settings page is directly visible in the admin menu and not as a sub menu for plugins.

Other Resources that don’t provide much help:


//Add the menu to the Plugins set of menu items
/*
add_plugins_page(
‚Converseon Toolkit Options‘, // The title to be displayed in the browser window for this page.
‚Converseon Toolkit Options‘, // The text to be displayed for this menu item
‚manage_options‘, // Which type of users can see this menu item
‚converseon_toolkit_options‘, // The unique ID – that is, the slug – for this menu item
array(
$this, ‚render_settings_page_content‘, // The name of the function to call when rendering this menu’s page
1
) // The position in the menu order this item should appear.
);
*/

add_menu_page(
$this->plugin_name, // The text to be displayed in the title tags of the page when the menu is selected.
‚Converseon Toolkit‘, // The text to be used for the menu.
‚administrator‘, // The capability required for this menu to be displayed to the user.
‚converseon_toolkit_options‘, // The slug name to refer to this menu by. Should be unique for this menu page
array($this, ‚render_settings_page_content‘), // The function to be called to output the content for this page.
plugin_dir_url(__FILE__) . ‚images/Converseon_C_Icon_20x20_grey.png‘, // The URL to the icon to be used for this menu.
101 // The position in the menu order this item should appear.
);

Dieser Beitrag hat 7 Kommentare

  1. About moving the settings page to a different page. This might help for other readers. I changed the add_plugins_page function to the add_menu_page. This way the settings page is directly visible in de admin menu and not as a sub menu for plugins.

    //Add the menu to the Plugins set of menu items
    /*add_plugins_page(
    ‚Converseon Toolkit Options‘, // The title to be displayed in the browser window for this page.
    ‚Converseon Toolkit Options‘, // The text to be displayed for this menu item
    ‚manage_options‘, // Which type of users can see this menu item
    ‚converseon_toolkit_options‘, // The unique ID – that is, the slug – for this menu item
    array(
    $this, ‚render_settings_page_content‘, // The name of the function to call when rendering this menu’s page
    1
    ) // The position in the menu order this item should appear.
    );*/

    add_menu_page(
    $this->plugin_name, // The text to be displayed in the title tags of the page when the menu is selected.
    ‚Converseon Toolkit‘, // The text to be used for the menu.
    ‚administrator‘, // The capability required for this menu to be displayed to the user.
    ‚converseon_toolkit_options‘, // The slug name to refer to this menu by. Should be unique for this menu page
    array($this, ‚render_settings_page_content‘), // The function to be called to output the content for this page.
    plugin_dir_url(__FILE__) . ‚images/Converseon_C_Icon_20x20_grey.png‘, // The URL to the icon to be used for this menu.
    101 // The position in the menu order this item should appear.
    );

  2. Please do not criticise other resources when yours is far from complete.

    Take, for example, this line:

    „Make sure to move it to a proper place (currently it appears below the „plugins“ menu though it belongs unsder „settings“).“

    Yes. So why does your tutorial not do that? And where are the instructions for that? In fact, when you follow other instructions to put it in the Settings or the main admin menu, you get told a completely different way of doing it.

    As developers lets not recommend complicated over-bloated boilerplates to do something this simple. Endless classes and convoluted code does not make us better developers

  3. Parse error: syntax error, unexpected ‚private‘ (T_PRIVATE), expecting end of file…
    admin/class-enym-admin.php

    Yet another ‚tutorial‘ that doesnt actually work. Thanks.

Schreibe einen Kommentar zu Jeroen Antworten abbrechen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

An den Anfang scrollen