WordPress.org

Ready to get started?Download WordPress

Forums

[resolved] Plugin options not appearing on options page using tabbed navigation (2 posts)

  1. Christiaan Conover
    Member
    Posted 1 month ago #

    I'm trying to set up my plugin's options page to use tabbed navigation via the Settings API, but the options themselves will not appear on the page. The tabs are visible, just not the settings fields.

    Here is the code to create my options page:

    /*
    	===== Admin Plugin Options =====
    	*/
    	// Create submenu entry under the Settings menu
    	function options_menu() {
    		add_options_page(
    			self::NAME, // Page title. This is displayed in the browser title bar.
    			self::NAME, // Menu title. This is displayed in the Settings submenu.
    			'manage_options', // Capability required to access the options page for this plugin
    			self::ID, // Menu slug
    			array( &$this, 'options_page' ) // Function to render the options page
    		);
    	} // End options_menu()
    
    	// Set up options page
    	function options_init() {
    		/*
    		Buffer Profiles
    		Buffer uses profiles to store the social media accounts attached to a Buffer account. We will retrieve all
    		social media profiles every time the plugin options page is loaded. This will only happen when the following
    		conditions are met:
    		- Plugin is fully authenticated with Buffer
    		- The plugin options page is being displayed
    		If the profiles are successfully retrieved, we can display the Buffer options. If not, we'll throw an error
    		and no Buffer options will be shown, since we have no data from which to create them.
    		*/
    		if ( $this->api->is_site_authenticated() && ( isset( $_REQUEST['page'] ) && self::ID == $_REQUEST['page'] ) ) {
    			// Get Buffer profiles for specified Buffer account
    			$this->profile = $this->api->get_profile( $this->options['site_access_token'] );
    
    			// If WordPress returns an error, notify the user
    			if ( is_wp_error( $this->profile ) ) {
    				echo '<div class="error settings-error"><p><strong>Uh oh! We had a problem getting the social media accounts tied to your Buffer account. Let\'s try again.</strong><br><em>WordPress Error: ' . $this->profile->get_error_message() . '</em></p></div>';
    			}
    			// If Buffer returns an error, notify the user
    			elseif ( ! empty( $this->profile['code'] ) ) {
    				echo '<div class="error settings-error"><p><strong>Uh oh! We had a problem getting the social media accounts tied to your Buffer account. Let\'s try again.</strong><br><em>API Error: ' . $this->profile['code'] . ' ' . $this->profile['error'] . '</em></p></div>';
    			}
    			// Otherwise the profile data is valid, so we can add the Buffer options to the page
    			else {
    				// Set defaults for any Buffer profiles not saved in plugin options
    				$this->buffer_profile_defaults();
    
    				// Set the array with list of enabled services
    				$this->service = $this->services_list( $this->profile );
    
    				// Register the settings for each service
    				foreach ( $this->service as $service ) {
    					register_setting(
    						self::PREFIX . $service, // The namespace for plugin options fields. This must match settings_fields() used when rendering the form.
    						self::PREFIX . 'options', // The name of the plugin options entry in the database.
    						array( &$this, 'options_validate' ) // The callback method to validate plugin options
    					);
    				}
    
    				// Call the Buffer options
    				$this->buffer_options();
    			}
    		}
    
    		// Register the Buffer authentication settings
    		register_setting(
    			self::PREFIX . 'buffer_auth', // The namespace for plugin options fields. This must match settings_fields() used when rendering the form.
    			self::PREFIX . 'options', // The name of the plugin options entry in the database.
    			array( &$this, 'options_validate' ) // The callback method to validate plugin options
    		);
    
    		// Load plugin options for Buffer authentication
    		$this->auth_options();
    
    		// Load scripts and stylesheets for the Options page
    		add_action( 'admin_enqueue_scripts', array( &$this, 'options_scripts' ) );
    	} // End options_init()
    
    	/* Buffer Options */
    	// Generate plugin options fields from profiles
    	function buffer_options() {
    		// Iterate through each profile
    		foreach ( $this->profile as $profile ) {
    			// Add a settings section for each type of social network
    			add_settings_section(
    				self::PREFIX . $profile['service'], // Name of the section
    				null, // Title of the section, unneeded here because it's handled by the tabbed navigation
    				null, // Callback for the section - unneeded for this plugin
    				self::ID // Page ID for the options page
    			);
    
    			// Create a settings field to manage each social media profile
    			add_settings_field(
    				$profile['id'], // Field ID (use the profile ID from Buffer)
    				'<img class="buffer_profile_avatar" src="' . $profile['avatar_https'] . '" alt="Avatar for ' . $profile['service'] . ' - ' . $profile['formatted_username'] . '"><span class="buffer_profile_username">' . $profile['formatted_username'] . '</span>', // Field title/label displayed to the user, includes avatar for profile (use the formatted username from Buffer)
    				array( &$this, 'buffer_settings_field_callback' ), // Callback method to display the option field
    				self::ID, // Page ID for the options page
    				self::PREFIX . $profile['service'], // Settings section in which to display the field
    				$profile // Send all the profile details to the callback method as an argument
    			);
    		}
    	} // End buffer_options()
    
    	// Authentication options
    	function auth_options() {
    		// Options section
    		add_settings_section(
    			self::PREFIX . 'buffer_auth', // Name of the section
    			null, // Title of the section, unneeded here because it's handled by the tabbed navigation
    			array( &$this, 'auth_callback' ), // Callback method to display plugin options
    			self::ID // Page ID for the options page
    		);
    
    		// If the Client ID and Client Secret are not stored in the database, show the fields for those items
    		if ( empty( $this->options['client_id'] ) || empty( $this->options['client_secret'] ) ) {
    			// Buffer application client ID
    			add_settings_field(
    				'client_id', // Field ID
    				'Client ID', // Field title/label, displayed to the user
    				array( &$this, 'client_id_callback' ), // Callback method to display the option field
    				self::ID, // Page ID for the options page
    				self::PREFIX . 'buffer_auth' // Settings section in which to display the field
    			);
    
    			// Buffer application client secret
    			add_settings_field(
    				'client_secret', // Field ID
    				'Client secret', // Field title/label, displayed to the user
    				array( &$this, 'client_secret_callback' ), // Callback method to display the option field
    				self::ID, // Page ID for the options page
    				self::PREFIX . 'buffer_auth' // Settings section in which to display the field
    			);
    		}
    
    		// Buffer access token to be used globally for the site (only show if Client ID and Client Secret are saved)
    		if ( ! empty( $this->options['client_id'] ) && ! empty( $this->options['client_secret'] ) ) {
    			// If no access token is saved in the database, display a static field label
    			if ( empty( $this->options['site_access_token'] ) ) {
    				// Add the settings field
    				add_settings_field(
    					'site_access_token', // Field ID
    					'Connect to Buffer', // Field title/label, displayed to the user
    					array( &$this, 'site_access_token_callback' ), // Callback method to display the option field
    					self::ID, // Page ID for the options page
    					self::PREFIX . 'buffer_auth' // Settings section in which to display the field
    				);
    			}
    			// If it is set, provide the option to disconnect from Buffer
    			else {
    				// Add the settings field
    				add_settings_field(
    					'buffer_oauth_disconnect', // Field ID
    					'Disconnect from Buffer', // Field title/label, displayed to the user
    					array( &$this, 'buffer_oauth_disconnect_callback' ), // Callback method to display the option field
    					self::ID, // Page ID for the options page
    					self::PREFIX . 'buffer_auth' // Settings section in which to display the field
    				);
    			}
    		}
    	} // End auth_options()
    
    	/* Plugin options callbacks */
    	// Callback for dynamically generated Buffer settings fields
    	// @param array $args arguments passed to the callback from the settings field
    	function buffer_settings_field_callback( $args ) {
    		// If this profile is enabled in plugin options, check the box
    		if ( ! empty( $this->options['profiles'][$args['id']]['enabled'] ) ) {
    			$checked = 'checked';
    		}
    		// If not, leave the box unchecked
    		else {
    			$checked = null;
    		}
    
    		// Create checkbox for enabling publishing to this service
    		echo '<p>Enabled? <input id="' . self::PREFIX . 'options_profiles_' . $args['id'] . '_enabled" name="' . self::PREFIX . 'options[profiles][' . $args['id'] . '][enabled]" type="checkbox" ' . $checked . '></p>';
    
    		// Create text input for post message
    		echo '<p>Message <input id="' . self::PREFIX . 'options_profiles_' . $args['id'] . '_message" name="' . self::PREFIX . 'options[profiles][' . $args['id'] . '][message]" type="text" value="' . $this->options['profiles'][$args['id']]['message'] . '" size=40></p>';
    	} // End buffer_settings_field_callback()
    	/* End plugin options callbacks */
    
    	// Authorization section
    	function auth_callback() {
    		// If client ID & secret haven't yet been saved, display this message
    		if ( empty( $this->options['client_id'] ) || empty( $this->options['client_secret'] ) ) {
    			// Set the callback URL. Do not encode for a URL string.
    			$callbackurl = $this->api->optionsurl();
    
    			// Display the message
    			echo '<p style="color: #E30000; font-weight: bold;">In order to use this plugin, you need to <a href="https://bufferapp.com/developers/apps/create" target="_blank">register it as a Buffer application</a></p><p>It\'s easy! Once you\'ve registered the application, copy the Client ID and Client Secret from the email you receive and paste them here.</p><p><strong>Callback URL</strong>: <a href="' . $callbackurl . '">' . $callbackurl . '</a></p>';
    		}
    		// If they have been saved, check whether there's an access token. If not, inform the user.
    		else {
    			if ( empty( $this->options['site_access_token'] ) && empty( $_REQUEST['code'] ) && empty( $_REQUEST['error'] ) ) {
    				echo '<div class="updated settings-error"><p><strong>You\'re almost done!</strong><br>Click the button below to authenticate this site with your Buffer account.</p></div>';
    			}
    		}
    	} // End auth_callback()
    
    	// Client ID
    	function client_id_callback() {
    		echo '<input type="text" name="' . self::PREFIX . 'options[client_id]" id="' . self::PREFIX . 'options_client_id" value="' . $this->options['client_id'] . '" size=40>';
    	} // End client_id_callback()
    
    	// Client secret
    	function client_secret_callback() {
    		// If client secret is saved in the database, the field is type 'password'. If not, it's type 'text'.
    		if ( ! empty( $this->options['client_secret'] ) ) {
    			echo '<input type="password" name="' . self::PREFIX . 'options[client_secret]" id="' . self::PREFIX . 'options_client_secret" value="' . $this->options['client_secret'] . '" size=40>';
    		}
    		else {
    			echo '<input type="text" name="' . self::PREFIX . 'options[client_secret]" id="' . self::PREFIX . 'options_client_secret" value="' . $this->options['client_secret'] . '" size=40>';
    		}
    	} // End client_id_callback()
    
    	// Access token
    	function site_access_token_callback() {
    		// If access token is not set, run the process to retrieve it
    		if ( empty( $this->options['site_access_token'] ) ) {
    			// Call the OAuth method
    			$this->api->buffer_oauth_connect();
    		}
    	} // End client_id_callback()
    
    	// Buffer OAuth disconnect
    	function buffer_oauth_disconnect_callback() {
    		// Checkbox input field
    		echo '<input type="checkbox" name="' . self::PREFIX . 'options[oauth_disconnect]" id="' . self::PREFIX . 'options_oauth_disconnect" value="yes">';
    		echo '<p class="description"><strong>WARNING:</strong> checking this box will remove the account credentials for the Buffer user currently associated with this plugin.</p>';
    	}
    
    	// Validate plugin options
    	function options_validate( $input ) {
    		// Set a local variable for the existing plugin options. This is so we don't mix up data.
    		$options = $this->options;
    
    		// If client ID and client secret have been changed from what's in the database, validate them
    		if ( empty( $this->options['client_id'] ) || empty( $this->options['client_secret'] ) ) {
    			// Check to make sure whether the provided values are hexadecimal
    			if ( ctype_xdigit( $input['client_id'] ) && ctype_xdigit( $input['client_secret'] ) ) {
    				$options['client_id'] = $input['client_id']; // Application client ID
    				$options['client_secret'] = $input['client_secret']; // Application client secret
    			}
    			// If either one of them is not hexadecimal, throw an error
    			else {
    				add_settings_error (
    					self::ID, // Setting to which the error applies
    					'client-auth', // Identify the option throwing the error
    					'Hang on a second! The client ID or client secret you entered doesn\'t match Buffer\'s format. Double-check them both, and take another crack at it.', // Error message
    					'error' // The type of message it is
    				);
    			}
    		}
    
    		// Access token will only be saved if Client ID and Client Secret are both already saved, but no access token is saved
    		if ( ! empty( $this->options['client_id'] ) && ! empty( $this->options['client_secret'] ) && empty( $this->options['site_access_token'] ) ) {
    			// Make sure a value is provided for the access token
    			if ( ! empty( $input['site_access_token'] ) ) {
    				// Only perform the validation tasks if the value has changed from what's in the database
    				if ( $input['site_access_token'] != $this->options['site_access_token'] ) {
    					// Query the plugin API to validate the access token
    					$apiresult = $this->api->get_user( $input['site_access_token'] );
    
    					// If the API returns a user ID, and the user ID is hexadecimal, the access token is valid
    					if ( ! empty( $apiresult['id'] ) && ctype_xdigit( $apiresult['id'] ) ) {
    						$options['site_access_token'] = $input['site_access_token'];
    						$options['site_user_id'] = $apiresult['id'];
    
    						// Display a successful message on the next page load
    						add_settings_error (
    							self::ID, // Setting to which the message applies
    							'site-access-token', // Identify the option throwing the message
    							'Hooray! Your site is now fully authenticated with Buffer, and you\'re ready to go!', // Success message
    							'updated' // The type of message it is
    						);
    					}
    					// If we got an error back from Buffer, notify the user
    					elseif ( ! empty( $apiresult['code'] ) ) {
    						add_settings_error (
    							self::ID, // Setting to which the error applies
    							'site-access-token', // Identify the option throwing the error
    							'Uh oh! Buffer says that something went wrong. Let\'s give it another shot!<br><em>' . $apiresult['code'] . ' ' . $apiresult['error'] . '</em>', // Error message
    							'error' // The type of message it is
    						);
    					}
    					// If the result was a WordPress error, show the error
    					elseif ( is_wp_error( $apiresult ) ) {
    						add_settings_error (
    							self::ID, // Setting to which the error applies
    							'site-access-token', // Identify the option throwing the error
    							'Uh oh! WordPress had an error.<br><em>' . $apiresult->get_error_message() . '</em>', // Error message
    							'error' // The type of message it is
    						);
    					}
    				}
    			}
    			// If nothing is provided for the access token, throw an error
    			else {
    				add_settings_error (
    					self::ID, // Setting to which the error applies
    					'site-access-token', // Identify the option throwing the error
    					'Whoops! It looks like you haven\'t yet authenticated with Buffer, and we can\'t continue until that\'s done. Let\'s try again!', // Error message
    					'error' // The type of message it is
    				);
    			}
    		}
    
    		// If the site is fully authenticated, process the rest of the plugin options
    		if ( $this->api->is_site_authenticated() ) {
    			// If OAuth Disconnect is selected, remove the Buffer user credentials
    			if ( ! empty( $input['oauth_disconnect'] ) ) {
    				$options['site_access_token'] = null;
    				$options['site_user_id'] = null;
    			}
    			// If OAuth Disconnect is not set, process the Buffer profile settings
    			else {
    				// Set local variable for 'profiles' input
    				$profiles = $input['profiles'];
    
    				// Sanitize the values of the 'enabled' checkboxes
    				foreach ( $profiles as $id => $fields ) {
    					// Sanitize the 'enabled' checkbox
    					if ( ! empty( $fields['enabled'] ) ) {
    						$profiles[$id]['enabled'] = 'on';
    					}
    					else {
    						$profile[$id]['enabled'] = null;
    					}
    
    					// Sanitize the text input for the 'message' field
    					$profiles[$id]['message'] = sanitize_text_field( $fields['message'] );
    				}
    
    				// Save profiles options
    				$options['profiles'] = $profiles;
    			}
    		}
    
    		// Return the validated options
    		return $options;
    	} // End options_validate()
    
    	// Render options page
    	function options_page() {
    		// Make sure the user has the necessary privileges to manage plugin options
    		if ( ! current_user_can( 'manage_options' ) ) {
    			wp_die( 'Sorry, you do not have sufficient privileges to access the plugin options for ' . self::NAME . '.' );
    		}
    		?>
    
    		<div class="wrap">
    			<h2><?php echo self::NAME; ?></h2>
    
    			<?php
    			// Check to see if 'tab' is set, and if so get the value
    			if ( ! empty( $_GET['tab'] ) ) {
    				$active_tab = $_GET['tab'];
    			}
    			// If 'tab' is not set, default to the first service in the array
    			elseif ( empty( $_GET['tab'] ) && ! empty( $this->service ) ) {
    				$active_tab = $this->service[0];
    			}
    			// If neither 'tab' nor the service array are set, default to the Buffer Authentication tab
    			else {
    				$active_tab = 'buffer_auth';
    			}
    			?>
    
    			<h2 class="nav-tab-wrapper">
    			<?php
    			// If the service array is set, set up the tabs for the services
    			if ( ! empty( $this->service ) ) {
    				// Iterate through each service in the array to create each tab
    				foreach( $this->service as $service ) {
    					?>
    					<a href="?page=<?php echo self::ID; ?>&tab=<?php echo $service; ?>" class="nav-tab <?php echo $service == $active_tab ? 'nav-tab-active' : ''; ?>"><?php echo $this->apiconfig['services'][$service]['types']['profile']['name']; ?></a>
    					<?php
    				}
    			}
    			?>
    				<a href="?page=<?php echo self::ID; ?>&tab=buffer_auth" class="nav-tab <?php echo 'buffer_auth' == $active_tab ? 'nav-tab-active' : ''; ?>">Buffer Authentication</a>
    			</h2><!-- .nav-tab-wrapper -->
    
    			<form action="options.php" method="post">
    				<?php
    				settings_fields( self::PREFIX . $active_tab ); // Retrieve the fields created for the current tab
    				do_settings_sections( self::PREFIX . $active_tab ); // Display the section for the current tab
    
    				// Show the submit button on any screen other than OAuth authorization
    				if ( ! ( ! empty( $this->options['client_id'] ) && ! empty( $this->options['client_secret'] ) && empty( $this->options['site_access_token'] ) ) ) {
    					submit_button(); // Form submit button generated by WordPress
    				}
    				?>
    			</form>
    		</div>
    		<?php
    	} // End options_page()
    
    	// Scripts and stylesheets for Options page
    	function options_scripts() {
    		//Load stylesheet
    		wp_enqueue_style(
    			self::ID, // Handle for the script
    			plugins_url( 'admin/assets/css/options.css', $this->pluginfile ), // Location of the stylesheet
    			array(),
    			self::VERSION
    		);
    	}
    	/*
    	===== End Admin Plugin Options =====
    	*/

    Here is the same code in the Github commit where it was added, in case you'd like to see it in relation to the rest of the plugin code.

    What am I missing? I should note that the options page was working without issue before I added tabbed navigation, which you can see in this commit.

    Any help is appreciated, I'm sure the answer it staring me in the face but I'm just not seeing it.

  2. Christiaan Conover
    Member
    Posted 4 weeks ago #

    I figured out the problem. I was referencing the wrong page ID in my add_settings_section() call.

Reply

You must log in to post.

About this Topic