Easiest way to hide (some) WordPress plugins from users? - wordpress

I'm using WordPress to make my users make their own website/blog. I have a set up that I'm cloning out to all the users with some special user-roles and standard plugins.
However, some of the plugins are not supposed to be changed or inactivated by the users.
Is their any way to select which plugins different user roles are allowed to use? Or a easy way to hide some plugins in the plugins-page but still have them working as normal?
Maybe there's some plugin that helps me to do this?

You could write a plugin that uses the "all_plugins" filter hook to remove from the array plugins that you don't want displaying for a certain user. Something like this:
$plugin_credentials = array(
'bob' => array(
'Hello Dolly' => 1
),
'jim' => array(
'Akismet' => 1,
'Hello Dolly' => 1,
),
'admin' => "**ALL**"
);
function plugin_permissions($plugins)
{
global $current_user, $plugin_credentials;
$username = $current_user->user_login;
if ($plugin_credentials[$username] == "**ALL**")
return $plugins;
$viewable_plugins = array();
foreach ($plugins as $plugin) {
if (isset($plugin_credentials[$username]) &&
isset($plugin_credentials[$username][$plugin['Name']]) &&
$plugin_credentials[$username][$plugin['Name']] == 1) {
array_push($viewable_plugins, $plugin);
}
}
return $viewable_plugins;
}
add_filter('all_plugins', 'plugin_permissions');
Managing the user permissions in the plugin itself is not ideal, but it is probably easiest. You can expand on that idea to create admin pages for managing the users and their viewable plugins in a database table somewhere.

Each plugin will usually specify their own role/permission, which you can see if you look at their add_submenu_page() or such function calls. You can create new roles for those plugins and replace the one specified by the author, but it will also break the changes if you upgrade the plugins.

You should stratify the users. Make sure that the Admin user(s) are trusted and know not to fiddle with what they don't understand. The others should be limited to their roles. Authors, editors, etc. For example, if they're just a part of the site to write articles, then they don't need to see the rest of it. Make them an author and be done with it.
This is part of client education. If its a smaller client with less stratified roles, then make them two accounts. Tell them "this is the account you administer the site with, you'll be using this rarely. And this is the account that you'll use most of the time to write and edit. You can do all of your daily tasks here and will most likely never need the administrator account". You won't always have luck with this approach, but its less time and effort invested in crap you shouldn't be wasting time on.

I've done a new version based on #spuriousdata Answer. This one uses the plugin slugs (file name minus the extension) to build the list of restrictions. This way is easier as we can unset the array using the first level $keys.
Configuration instructions in the code itself.
<?php
/**
* Plugin Name: Limit Plugins by User
* Plugin URI: http://stackoverflow.com/q/14340131/1287812
* Description: Show selected plugins for specific users.
* Based on the code by spuriousdata, http://stackoverflow.com/a/3713985.
* Author: brasofilo
* Author URI: http://wordpress.stackexchange.com/users/12615/brasofilo
* Version: 1.0
* License: GPLv2 or later
*/
add_filter( 'all_plugins', 'plugin_permissions_so_3707134' );
/**
* Filter the list of plugins according to user_login
*
* Usage: configure the variable $plugin_credentials, which holds a list of users and their plugins.
* To give full access, put a simple string "ALL"
* To grant only for some plugins, create an array with the Plugin Slug,
* which is the file name without extension (akismet.php, hello.php)
*
* #return array List of plugins
*/
function plugin_permissions_so_3707134( $plugins )
{
// Config
$plugin_credentials = array(
'admin' => "ALL",
'other-admin' => array(
'akismet',
),
'another-admin' => array(
'akismet',
'hello',
),
);
// Current user
global $current_user;
$username = $current_user->user_login;
// Super admin, return everything
if ( "ALL" == $plugin_credentials[ $username ] )
return $plugins;
// Filter the plugins of the user
foreach ( $plugins as $key => $value )
{
// Get the file name minus extension
$plugin_slug = basename( $key, '.php' );
// If not in the list of allowed plugins, remove from array
if( !in_array( $plugin_slug, $plugin_credentials[ $username ] ) )
unset( $plugins[ $key ] );
}
return $plugins;
}

Related

Headless Wordpress, Is there a way to access data in wp_options table as REST endpoint?

Wordpress has an awesome REST API interface.
https://developer.wordpress.org/rest-api/reference/
But the content in wp_options table seems to be missing REST support. Is there a way to access the content in wp_otions table as REST endpoint via plugins?. Thanks.
There is the settings endpoint, but it only contains a surprisingly limited amount of them it seems.
This is something you could very easily do yourself though. I'm not sure if any plugins do it, but I also wouldn't recommend a plugin for something that can be done with less than 20 lines of code.
You just need to register a route using register_rest_route() on the rest_api_init hook, and pass it a callback function. You can drop code like this in your functions.php file or create a Must Use Plugin and drop the code in there, either way.
add_action( 'rest_api_init', function () {
register_rest_route( 'my-custom-route/v1', '/opt/', array(
'methods' => 'GET',
'callback' => 'get_rest_option',
//'permission_callback' => function () {
// return current_user_can( 'administrator' );
//}
) );
} );
function get_rest_option( $data ) {
return get_option( $data['option_name'] );
}
The above will give you access to whatever option you want by accessing:
/wp-json/my-custom-route/v1/opt/?option_name=siteurl
I went ahead and dropped an example on a site of mine:
https://xhynk.com/content-mask/wp-json/my-custom-route/v1/opt/?option_name=blogname
https://xhynk.com/content-mask/wp-json/my-custom-route/v1/opt/?option_name=siteurl
However, this will potentially expose anything in your options table. I went ahead and commented out the permission_callback so that any person, signed in or not, can access it. However, I also added a check like this:
function get_rest_option( $data ) {
if( $data['option_name'] === 'siteurl' || $data['option_name'] === 'blogname' ){
return get_option( $data['option_name'] );
} else {
return 'Unauthorized. Use `siteurl` or `blogname`';
}
}
You can see that home will fail: https://xhynk.com/content-mask/wp-json/my-custom-route/v1/opt/?option_name=home
I would recommend adding in a valid array of options, or using the permission_callback in order to lock it down a bit. You could even have an access key instead, and keep that key secret. Either way, be aware of the security implications of exposing your entire wp_options table, and take some sort of preventative measure!

Algolia - Wordpress - exclude category from indexing

How can I exclude certain WordPress Page categories from being indexed in Algolia?
First of all, I would recommend you stick with the new version of the plugin. At the time of writing this, the latest version is 0.2.5. Indeed the old version (0.0.1) will not be supported anymore.
Regarding your question, it is indeed possible to filter what posts you would like to push to Algolia and make searchable.
What I understand from your question is that you have pages assigned to categories, and you would like to avoid making pages from certain categories come up in search results. If these initial statements are wrong, please comment on this answer and I'd gladly push an update!
You can hook into the decision of indexing a post by using WordPress filters.
In your case, if you are willing to exclude the pages from the searchable_posts index you could use the algolia_should_index_searchable_post filter.
If you are willing to exclude the pages from the posts_page index, you could use the algolia_should_index_post filter.
Here is an example of how you could exclude all pages of a list of categories identified by their IDs.
<?php
// functions.php of your theme
// or in a custom plugin file.
// We alter the indexing decision making for both the posts index and the searchable_posts index.
add_filter('algolia_should_index_post', 'custom_should_index_post', 10, 2);
add_filter('algolia_should_index_searchable_post', 'custom_should_index_post', 10, 2);
/**
* #param bool $should_index
* #param WP_Post $post
*
* #return bool
*/
function custom_should_index_post( $should_index, WP_Post $post ) {
// Replace these IDs with yours ;)
$categories_to_exclude = array( 7, 22 );
if ( false === $should_index ) {
// If the decision has already been taken to not index the post
// stick to that decision.
return $should_index;
}
if ( $post->post_type !== 'page' ) {
// We only want to alter the decision making for pages.
// We we are dealing with another post_type, return the $should_index as is.
return $should_index;
}
$post_category_ids = wp_get_post_categories( $post->ID );
$remaining_category_ids = array_diff( $post_category_ids, $categories_to_exclude );
if ( count( $remaining_category_ids ) === 0 ) {
// If the post is a page and belongs to an excluded category,
// we return false to inform that we do not want to index the post.
return false;
}
return $should_index;
}
More information about extending the Algolia Search plugin for WordPress can be found on documentation: Basics of extending the plugin
Update:
The code has been updated to ensure it doesn't exclude a product if it is associated to multiple categories and not all of them are excluded.

get list of network enabled themes in wordpress multisite installation

I am working on a theme, where I want to get to know if the theme is network enabled or not for some functionality.
Can anyone explain me how can I get the list of network enabled themes? or just to know a theme is network enabled or not?
Any help is appreciated.
How about a 7.5 year too late answer?
I'm writing a plugin that needed this functionality too.
Unfortunately I couldn't find any clearly defined "network-enabled" functions, hooks, or DB table keys related to themes. Plugins get a little bit more love in that regard.
DB Info
With that said, network-activated plugins are stored in the main wp_sitemeta table with the key "allowedthemes".
Unfortunately, (yet again), it's not a consistent array ready for use as-is.
It contains EVERY theme "slug" as an array value with standard number index keys, but ALSO contains the network-activated themes with the theme "slug" as the key and a boolean "1" as the value. Why? I have no idea, but surely it made sense to someone, somewhere, at some point in time.
DB Serialized Meta Value
Example meta_value for meta_key "allowedthemes" in wp_sitemeta table in the DB:
a:8:{i:0;s:12:"twentytwenty";i:1;s:15:"twentytwentyone";i:2;s:17:"twentytwentythree";i:3;s:15:"twentytwentytwo";s:12:"twentytwenty";b:1;s:15:"twentytwentyone";b:1;s:17:"twentytwentythree";b:1;s:15:"twentytwentytwo";b:1;}
Retrieving Values
Getting this value depends on the type of multisite, and/or plugin compatibility you want to offer.
For single network multisite installs
$allowed_themes = get_site_option('allowedthemes');
For multinetwork multisite installs
$allowed_themes = get_network_option(get_current_network_id(), 'allowedthemes');
Results
echo '<pre>',print_r($allowed_themes),'</pre>';
// shows
Array
(
[0] => twentytwenty
[1] => twentytwentyone
[2] => twentytwentythree
[3] => twentytwentytwo
[twentytwenty] => 1
[twentytwentyone] => 1
[twentytwentythree] => 1
[twentytwentytwo] => 1
)
1
Results Breakdown
Array values with [#] => theme_slug are just installed/available themes.
Array values with [theme_slug] => 1 are network-activated themes.
Again, WHY mix them in one array like this? Couldn't tell you. It is what it is.
Making Results Useful
Now there's plenty of ways to extract JUST the network activated themes, or JUST the installed/available themes with array_walk functions and other techniques.
One (less elegant, but more thorough) way I do this within the plugin I'm writing is to loop through all the themes from wp_get_themes() and wherever a "theme slug" is the key, append it to an array for later use:
$all_themes = wp_get_themes();
$allowed_themes = get_site_option('allowedthemes');
foreach ($all_themes as $theme => $object) {
if (isset($allowed_themes[$theme]) && (bool) $allowed_themes[$theme]) {
$network_activated_themes[] = $theme;
}
}
echo '<pre>',print_r($network_activated_themes),'</pre>';
// shows
Array
(
[0] => twentytwenty
[1] => twentytwentyone
[2] => twentytwentythree
[3] => twentytwentytwo
)
1
/* ALTERNATE LOOP TO GET THEME OBJECT DATA */
foreach ($all_themes as $theme => $object) {
if (isset($allowed_themes[$theme]) && (bool) $allowed_themes[$theme]) {
$network_activated_themes[$theme] = $object;
}
}
echo '<pre>',print_r($network_activated_themes),'</pre>';
// shows an array identical to wp_get_themes(), but only with network-activated themes
Once you have an array of JUST network-activated themes like this, you should be able to accomplish your goals with network-activated themes.
I realize that #nitin-yawalkar probably doesn't need this help anymore, but due to the complete lack of answers here and elsewhere related to this question, I wanted to chime in and add SOMETHING to help steer people in the right direction.
UPDATE
I did find a filter of interest for this.
apply_filters( 'allowed_themes', string[] $allowed_themes )
https://developer.wordpress.org/reference/hooks/allowed_themes/
It's not very helpful as-is, but within proper context/scope, it's quite handy.
Example 'allowed_themes' Filter on wp-admin/themes.php page
One aspect of the plugin I'm developing allows setting allowed themes on a per-site basis on multisites. This filter is network-wide, so to apply it on a per-site basis you can do something like this:
add_filter('allowed_themes', 'tqc_show_allowed_themes');
function tqc_show_allowed_themes( $allowed_themes ) {
// make sure we're in the admin panel
if ( is_admin() ) {
// make sure we're on the themes.php page
global $pagenow;
if ( $pagenow === 'themes.php' ) {
//print the standard array
echo '<pre>',print_r( $allowed_themes ),'</pre>';
/*
* shows same array as
* get_site_option( 'allowedthemes' );
* and
* get_network_option( get_current_network_id(), 'allowedthemes' );
*
Array
(
[0] => twentytwenty
[1] => twentytwentyone
[2] => twentytwentythree
[3] => twentytwentytwo
[twentytwenty] => 1
[twentytwentyone] => 1
[twentytwentythree] => 1
[twentytwentytwo] => 1
)
1
*
*/
// a separate custom function that retrieves valid themes
// allowed to be used by the current site
$valid_themes = tqc_get_valid_themes( get_current_blog_id() );
// set allowed themes to empty
$allowed_themes = array();
// loop through once to build out the [#] => theme_slug part
foreach( $valid_themes as $theme => $values ) {
$allowed_themes[] = $theme;
}
// loop through again to build out the [theme_slug] => 1 part
foreach( $valid_themes as $theme => $values ) {
$allowed_themes[ $theme ] = (bool) true;
}
}
}
// print the new array
echo '<pre>',print_r( $allowed_themes ),'</pre>';
/*
* shows modified array of allowed themes for this specific site
*
Array
(
[0] => twentytwenty
[1] => twentytwentyone
[twentytwenty] => 1
[twentytwentyone] => 1
)
1
*
*/
// return array to the filter to be applied
return $allowed_themes;
}
The 'allowed_themes' filter applies network-wide. Use context/scope like the example above to make it useful per-site / per-role / per-user, and limit it so that it's only applied when / where you need to alter the allowed themes.

Wordpress Validate New Nicename/Slug

I have written a custom "Edit Account" script that allows a Wordpress user to update their Wordpress account. Everything is working great, except that I can't seem to find a way to update the user's nicename, which also doubles as the user's URL slug (via the get_author_posts_url function). This is causing issues because when a user changes their name, their slug still contains their original name - not the new one.
I know that the sanitize_title function will generate the new nicename, but I don't know how to verify that it is unique and modify it if it is not before entering it into the DB. I am wondering what built-in functions Wordpress has to handle this. I know I can write my own script to do this, but I would much rather use Wordpress functions. I couldn't find this anywhere in the WP documentation. Thanks!
Here is a function I have written to in lue of a built in function:
function new_user_slug($string){
//GENERATE NEW SLUG
$slug=sanitize_title($string);
//MAKE SURE SLUG IS UNIQUE
$result=mysql_query("SELECT * FROM wp_users WHERE user_nicename='$slug'");
if(mysql_num_rows($result)==0){
return $slug;
}else{
$counter=2;
$kill=0;
while($kill==0){
$mod_slug=$slug."-".$counter;
$result=mysql_query("SELECT * FROM wp_users WHERE user_nicename='$mod_slug'");
if(mysql_num_rows($result)==0){
$kill=1;
}else{
$counter++;
}
}
return $mod_slug;
}
}
This takes a string (the user's updated name) and converts it into the default slug. It then checks the slug against the database to see if it is unique. If it is, the slug is returned. If not, it enters an iteration loop that incrementally changes the slug until it is unique.
Try this:
wp_unique_post_slug( $slug, $post_ID, $post_status, $post_type, $post_parent )
Source: https://codex.wordpress.org/Function_Reference/wp_unique_post_slug
Actually if you use wordpress user functions like wp_insert_user and wp_update_user Wordpress itselt handles duplicate user_nicename entries and adds -n suffix to them.
So your code would be something like this:
function new_nicename( $user_id, $nicename ) {
$nicename = sanitize_title( $nicename );
$user_id = wp_update_user( array( 'ID' => $user_id, 'user_nicename' => $nicename ) );
}

Change owner on publish node in Drupal

Users on my site can add nodes of a custom type (let's call it "Player") but cannot publish them. Effectively they need moderating before posting. Once an admin / moderator has published them, I want the owner / publisher to be changed to an the relevant admin / moderator. This is so that the user is be unable to edit them and also so it is possible to track who approved them etc.
How do I go about this? I thought it might involve Actions / Rules / Workflow / Workflow-ng etc, but I've looked at each and can't seem to figure out how to make it work!
Another alternative is to write a short module that includes an 'approve' link using hook_link(). Point that link to a menu callback that changes the node's ownership from the current user to the user that clicked the 'Approve' link.
It could be nice, clean way of solving this, but requires a bit of Drupal knowhow. However, if you ask someone in the #drupal IRC channel on irc.freenode.net, they could show you how to get started, or even code it as a contributed module for you.
You can do it manually when you're editing the Player nodes. There's a group of two settings towards the end where you can change the node creator and creation time.
Alternatively, can you give the non-admin users permission to create nodes, but remove their permission to edit these nodes. Might work, but could be painful for these users.
Just to add some more info - BrainV helped me develop the following code for a custom module - called publishtrigger here. I wanted the approve button to publish the Player node and then assign it to the "contentadmin" user, which has ID 6 in my case...
<?php
/**
* Implementation of hook_perm().
*/
function publishtrigger_perm() {
return array('approve nodes');
}
/**
* Implementation of hook_menu().
*/
function publishtrigger_menu() {
$items['approve/%'] = array(
'title' => 'Approve',
'page callback' => 'publishtrigger_approve_node',
'page arguments' => array(1),
'access arguments' => array('approve nodes'),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Implementation of hook_link().
*/
function publishtrigger_link($type, $object, $teaser = FALSE) {
// Show this link at the bottom of nodes of the Player type which are not yet
// owned by contentadmin (UID 6).
if ($type == 'node' && $object->type == 'player') {
// Make sure user has permission to approve nodes.
if (user_access('approve nodes')) {
$links = array();
if ($object->uid != 6 || $object->status == 0) {
// Node is not owned by contentadmin (UID 6), and therefore not approved.
$links['approve_link'] = array(
'title' => 'Approve',
'href' => 'approve/' . $object->nid,
);
}
else {
// Node is already approved
$links['approve_link'] = array('title' => 'Already approved');
}
return $links;
}
}
}
/**
* When this code is run, adjust the owner of the indicated node to 'contentadmin',
* UID 6.
*
* #param $nid
* The node id of the node we want to change the owner of.
*/
function publishtrigger_approve_node($nid) {
// Load the node.
$node = node_load($nid);
// Set the UID to 6 (for contentadmin).
$node->uid = 6;
// Publish the node
$node->status = 1;
// Save the node again.
node_save($node);
// Go back to the node page
drupal_goto($node->path);
}

Resources