I use Gutenberg with WordPress for a website with students.
I would like to display a list with all students (roles : student)
and exclude from the list the student who is logged in.
I tried two solutions.
First solution with getUsers() function. When I'm logged like an administrator all works fine but when a student is logged, he does not have permission to view the list. Only administrators have permission.
Second solution with a custom API route. I got a promise pending.
First solution :
import { __ } from '#wordpress/i18n';
import { CheckboxControl } from '#wordpress/components';
import { registerPlugin } from '#wordpress/plugins';
import { PluginDocumentSettingPanel } from '#wordpress/edit-post';
import { useSelect, useDispatch } from '#wordpress/data';
import { useEntityProp } from '#wordpress/core-data';
import { useState, setState, useEffect } from '#wordpress/element';
const metaboxStudents = () => {
const postType = useSelect( ( select ) => {
return select( 'core/editor' ).getCurrentPostType();
});
if ( postType !== 'subject-imposed' ) {
return null;
}
const [ meta, setMeta ] = useEntityProp( 'postType', postType, 'meta' );
const authors = useSelect( ( select ) => {
return select( 'core' ).getUsers( { roles: 'student' } );
}, [] );
if ( !posts ) {
return null;
}
const handleCheckboxChange = (data) => {
const isChecked = meta._metafield_students.some(checkedCheckbox => checkedCheckbox === data);
if (isChecked) {
setMeta( { _metafield_students: meta._metafield_students.filter( ( checkedCheckbox) => checkedCheckbox !== data) } );
} else {
setMeta( { _metafield_students: meta._metafield_students.concat(data) } );
}
};
return(
<PluginDocumentSettingPanel
name="list-students"
title={ __( 'List of students', 'ccn-gut' ) }
className='editor-styles-metabox'
>
<div className="gut-checkboxes-group">
{ posts.map( ( data ) => (
wp.data.select("core").getCurrentUser().id !== data.id
? (
<CheckboxControl
label={ data.name }
key={`student-${data.id}`}
value={ data.id }
checked={ meta._metafield_students.some(checkedCheckbox => checkedCheckbox === data.id) }
onChange={ () => handleCheckboxChange(data.id) }
/>
) : null
) ) }
</div>
</PluginDocumentSettingPanel>
);
};
registerPlugin('plugin-document-students', {
render: metaboxStudents,
icon: null
});
Second solution :
PHP for my WordPress plugin :
wp_localize_script( 'wp-api', 'wpApiSettings', array(
'root' => esc_url_raw( rest_url() ),
'nonce' => wp_create_nonce( 'wp_rest' )
));
PHP for API route :
function student_api_rest() {
register_rest_route('api/v1/', 'students', array(
'methods' => 'GET',
'callback' => 'student_api_results'
));
}
function student_api_results($data) {
....
}
index.js :
import apiFetch from '#wordpress/api-fetch';
wp.apiFetch.use( apiFetch.createNonceMiddleware( wpApiSettings.nonce ) );
const [users, setUsers] = useState( null );
useEffect( () => {
wp.apiFetch( { path: '/api/v1/students' } ).then(
(result) => {
setUsers( result );
}
)
}, []);
console.log(users);
Which solution to choose and how to resolve one of those two solutions? Permission VS promise Pending
you should be able to add capabilities to the custom user types of "students". look for where the student role was activated, it should look something like this
add_role( $role, $display_name, $capabilities );
and your looking to add list_users as a capability I believe. You can find the full list of capabilities here https://wordpress.org/support/article/roles-and-capabilities/ and heres the link directly to the list_users section of that https://wordpress.org/support/article/roles-and-capabilities/#list_users
Related
I have created a rather complex Gutenebrg Block
I have hardcoded the Post / Project IDs and I am now struggling how to implement the update function. I use two different API Routes and want to give the user the possibilty to change the Block Content via typing in the ID in a field.
Passing an attribute into :
withSelect(select => {
return {
posts: select('core').getEntityRecords('/wp/v2', 'projects', {include: 2962}),
posts1: select('core').getEntityRecords('/wp/v2', 'posts', {include: 3432})
};
})
Most of the code below… It was rather tricky for me as a Gutenberg rookie...
/**
* Block dependencies
*/
import './style.scss';
/**
* Internal block libraries
*/
const { __ } = wp.i18n;
const { registerBlockType } = wp.blocks;
const { Spinner } = wp.components;
const { withSelect, useSelect } = wp.data;
const {
InspectorControls,
RichText,
MediaUpload,
URLInputButton
} = wp.blockEditor;
var dispatch = wp.data.dispatch;
dispatch( 'core' ).addEntities( [
{
name: 'projects', // route name
kind: '/wp/v2', // namespace
baseURL: '/wp/v2/projects/' // API path without /wp-json
},
{
name: 'posts', // route name
kind: '/wp/v2', // namespace
baseURL: '/wp/v2/posts/' // API path without /wp-json
}
]);
registerBlockType(
'jsforwpblocks/dynamic',
{
title: __( 'Example - Dynamic Block', 'jsforwpblocks'),
description: __( 'A look at how to build a basic dynamic block.', 'jsforwpblocks'),
icon:'shield',
category: 'widgets',
attributes: {
projektID: {
type: 'string',
},
},
edit:
withSelect( select => {
return {
posts: select( 'core' ).getEntityRecords( '/wp/v2', 'projects',{include: 2962}),
posts1: select( 'core' ).getEntityRecords( '/wp/v2', 'posts',{include: 3432})
};
} )
( ( { props, posts, posts1, className, isSelected, setAttributes,attributes } ) => {
if ( ! posts ) {
return (
<p className={className} >
<Spinner />
{ __( 'Loading Posts', 'jsforwpblocks' ) }
</p>
);
}
if ( 0 === posts.length ) {
return <p>{ __( 'No Posts', 'jsforwpblocks' ) }</p>;
}
function changeprojektid(projektID) {
// using some nice js features instead of typing
// { heading: heading }
setAttributes({ projektID });
withSelect( select => {
let query = 'include: 2962'
return {
posts: select( 'core' ).getEntityRecords( '/wp/v2', 'projects', {include: 2962})
};
} )
}
return (
<div>
<div className="copy">
<RichText
className="heading"
tagName="h2"
placeholder="Enter your ID"
value={attributes.projektID}
onChange={changeprojektid}
/>
</div>
<ul className={ className }>
{ posts.map( post => {
return (
<li>
<a className={ className } href={ post.link }>
{ post.title.rendered }
<img src={ post.projimage1.guid } />
</a>
</li>
);
}) }
</ul>
<ul className={ className }>
{ posts1.map( post => {
return (
<li>
<a className={ className } href={ post.link }>
{ post.title.rendered }
</a>
</li>
);
}) }
</ul>
</div>
);
} )
// end withAPIData
, // end edit
I try to add two new panels to the existing gutenberg document sidebar. One should contain a radio-button menu to set the height of the header image, and the other one a text-field to enter a subtitle for the page.
But because I do not want to use the outdated meta boxes technologie, there aren't hardly any tutorials how to accomplish this. I only found the following piece of code, but I have no idea how to shape it to my needs and where to put it ;) - My knowledge of coding is just not good enough, but I still need to implement this feature in my theme.
const { registerPlugin } = wp.plugins
const { PluginDocumentSettingPanel } = wp.editPost
const PluginDocumentSettingPanelDemo = () => (
<PluginDocumentSettingPanel
name="custom-panel"
title="Custom Panel"
className="custom-panel"
>
Custom Panel Contents
</PluginDocumentSettingPanel>
)
registerPlugin('plugin-document-setting-panel-demo', {
render: PluginDocumentSettingPanelDemo
})
Do you maybe have a guess how to achieve my idea? Thanks for you support, and greetings from Austria! Samuel
First of all, register the meta fields, so you have where to save the values. This goes in your plugin file or functions.php.
register_post_meta('post', 'customname_meta_subtitle', array(
'show_in_rest' => true,
'type' => 'string',
'single' => true
));
register_post_meta('post', 'customname_meta_header_height', array(
'show_in_rest' => true,
'type' => 'string',
'single' => true
));
You can check the documentation. We are telling WordPress to create 2 new post meta fields, with keys customname_meta_subtitle and customname_meta_header_height, which we will use in the Gutenberg part.
For the ES code, you will need the following:
const { registerPlugin } = wp.plugins
const { PluginDocumentSettingPanel } = wp.editPost
const { RadioControl, TextControl } = wp.components
const { withState } = wp.compose
const { withSelect, withDispatch } = wp.data
let SubtitleControl = ({ subtitle, handleSubtitleChange }) => (
<TextControl
label="Set subtitle"
value={subtitle}
onChange={subtitle => handleSubtitleChange(subtitle)}
/>
);
SubtitleControl = withSelect(
(select) => {
return {
subtitle: select('core/editor').getEditedPostAttribute('meta')['customname_meta_subtitle']
}
}
)(SubtitleControl);
SubtitleControl = withDispatch(
(dispatch) => {
return {
handleSubtitleChange: (value) => {
dispatch('core/editor').editPost({ meta: { customname_meta_subtitle: value } })
}
}
}
)(SubtitleControl);
let HeaderImageHeightControl = ({ height, handleHeightChange }) => (
<RadioControl
label="Set image height"
help="Set the height of the header image"
selected={height}
options={[
{ label: '100', value: '1' },
{ label: '200', value: '2' },
]}
onChange={handleHeightChange}
/>
);
HeaderImageHeightControl = withSelect(
(select) => {
return {
height: select('core/editor').getEditedPostAttribute('meta')['customname_meta_header_height']
}
}
)(HeaderImageHeightControl);
HeaderImageHeightControl = withDispatch(
(dispatch) => {
return {
handleHeightChange: value => {
dispatch('core/editor').editPost({ meta: { customname_meta_header_height: value } })
}
}
}
)(HeaderImageHeightControl);
const PluginDocumentSettingPanelDemo = () => (
<PluginDocumentSettingPanel
name="custom-panel"
title="Custom Panel"
className="custom-panel"
>
<SubtitleControl />
<HeaderImageHeightControl />
</PluginDocumentSettingPanel>
)
registerPlugin('plugin-document-setting-panel-demo', {
render: PluginDocumentSettingPanelDemo
})
Most of this code is described in the official WP tutorial, but feel free to ask if anything is unclear.
Finally, to use the new values, you can do something like this:
<h1><?php echo get_post_meta(get_the_ID(), 'customname_meta_subtitle')[0]; ?></h1>
<h1><?php echo get_post_meta(get_the_ID(), 'customname_meta_header_height')[0]; ?></h1>
This is to be used in the post template file, for the front-end visualization of the meta field info.
I hope this helps!
I have followed this code from the site "http://carlofontanos.com/user-login-with-wordpress-using-react-native/"
I have done my Login.js like this
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
TextInput,
TouchableOpacity,
Keyboard
} from 'react-native';
import { AsyncStorage } from 'react-native';
import {Actions} from 'react-native-router-flux';
import { StackNavigator } from 'react-navigation';
export default class LoginForm extends Component<{}> {
constructor(props){
super(props)
this.state={
userEmail:'',
userPassword:'',
validating: false
}
}
login = () =>{
this.state.validating = true;
const {userEmail,userPassword} = this.state;
let reg = /^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/ ;
if(userEmail==""){
//alert("Please enter Email address");
this.setState({email:'Please enter Email address'})
}
else if(reg.test(userEmail) === false)
{
//alert("Email is Not Correct");
this.setState({email:'Email is Not Correct'})
return false;
}
else if(userPassword==""){
this.setState({email:'Please enter password'})
}
else{
fetch('http://siteURL/wetest/userlogin.php',{
method:'post',
header:{
'Accept': 'application/json',
'Content-type': 'application/json'
},
body:JSON.stringify({
email: userEmail,
password: userPassword
})
})
.then((response) => response.json())
.then((responseJson)=>{
let data = responseJson.data;
if (this.saveToStorage(data)){
this.setState({
validating: false
});
alert("Successfully Login");
/* Redirect to home page */
Actions.home()
} else {
alert("Wrong Login Details");
}
})
.catch((error)=>{
console.error(error);
});
}
Keyboard.dismiss();
}
render(){
return(
<View style={styles.container}>
<TextInput style={styles.inputBox}
underlineColorAndroid='rgba(0,0,0,0)'
placeholder="Email"
placeholderTextColor = "#ffffff"
selectionColor="#fff"
keyboardType="email-address"
onChangeText={userEmail => this.setState({userEmail})}
/>
<TextInput style={styles.inputBox}
underlineColorAndroid='rgba(0,0,0,0)'
placeholder="Password"
secureTextEntry={true}
placeholderTextColor = "#ffffff"
ref={(input) => this.password = input}
onChangeText={userPassword => this.setState({userPassword})}
/>
<TouchableOpacity style={styles.button} onPress={this.login} >
<Text style={styles.buttonText}>Login</Text>
</TouchableOpacity>
</View>
)
}
async saveToStorage(userData){
if (userData) {
await AsyncStorage.setItem('user', JSON.stringify({
isLoggedIn: true,
authToken: userData.auth_token,
id: userData.user_id,
name: userData.user_login
})
);
return true;
}
return false;
}
}
And i have done the server site code like this.
*
<?php
include 'wp-load.php';
$json = file_get_contents('php://input');
$obj = json_decode($json,true);
$email = $obj['email'];
$password = $obj['password'];
$response = array(
'data' => array(),
'msg' => 'Invalid email or password',
'status' => false
);
if($obj['email']!=""){
$creds = array();
$creds['user_login'] = $email;
$creds['user_password'] = $password;
$creds['remember'] = true;
$user = wp_signon( $creds, false );
if ( is_wp_error($user) ){
echo json_encode('Wrong Details');
}
else{
$user = get_user_by( 'email', $email );
if ( $user ){
$password_check = wp_check_password( $_POST['password'], $user->user_pass, $user->ID );
if ( $password_check ){
/* Generate a unique auth token */
$token = wp_generate_password( 30 );
/* Store / Update auth token in the database */
if( update_user_meta( $user->ID, 'auth_token', $token ) ){
/* Return generated token and user ID*/
$response['status'] = true;
$response['data'] = array(
'auth_token' => $token,
'user_id' => $user->ID,
'user_login' => $user->user_login
);
$response['msg'] = 'Successfully Authenticated';
//echo json_encode($response);
}
}
}
}
}
else{
echo json_encode('try again');
}
?>
*
If you can see the both code it is written for login and save the data in the device by "async saveToStorage". But for now it giving me the error.
The error is "json parse error unexpected eof". You can in the php code, I have tried with return the data by json_encode. That did not work also. Can i get some on this. I want to know where the exact error is ? Thanks in advance.
json-parse-error-unexpected-eof it's usually
an error when you are trying to convert non covertable response to json (like html response) into json
for safety I usually convert it first to string using .then(res => res.text()) then console.log() or alert() it to prove the response is convertable to json or not without showing the react-native's red-box message,
if the response is valid json you can convert it by yourself with JSON.parse(responseText)
my problem is that's error cause for me some time for one webservice and i call that another time that's ok without changing code!!!
For me I got this issue in react once I tried to parse non existed element so I fixed this by checking if the it exists or not
if (result.data.length >0){
return JSON.parse(result.data[0].assessment)
} else {
return null
}
We are using the google sitemap module: https://github.com/wilr/silverstripe-googlesitemaps
We have a TourPage.php which has custom routes on its controller, in our sitemap.xml it shows the parent page fine, but no routes are showing up:
e.g At the moment it just shows:
website.com/tours/tour-name-1/
website.com/tours/tour-name-2/
but we want it to show:
website.com/tours/tour-name-1/
website.com/tours/tour-name-1/photos
website.com/tours/tour-name-1/details
website.com/tours/tour-name-1/reviews
...
website.com/tours/tour-name-2/
website.com/tours/tour-name-2/photos
website.com/tours/tour-name-2/details
website.com/tours/tour-name-2/reviews
etc
How do we achieve this?
class TourPage_Controller extends Page_Controller {
private static $allowed_actions = array('BrochureForm', 'brochure', 'brochure_thanks', 'details', 'photos', 'reviews', 'book', 'downloadItineraryFile', 'map', 'overview', 'getSlugName');
public function photos() {
if(!$this->canViewPage()) {
return $this->redirect(ToursPage::getFirstPageLink());
}
return array(
'MetaTitle' => $this->resolveMetaTitle('MetaTitlePhotos'),
'Photos' => $this->TourPhotos(),
'MetaImage' => $this->resolveMetaImagePhotos(),
'MetaVideo' => false,
'ImageSlug' => $this->getSlugName(),
'SluggedImage' => $this->getImageBySlug(),
'AbsolutePhotoURL' => $this->getAbsolutePhotoURL()
);
}
public function index() {
if(!$this->canViewPage()) {
return $this->redirect(ToursPage::getFirstPageLink());
}
// Have to return something...
return array();
}
public function init() {
parent::init();
if($this->request->param('Action') == 'brochure') {
Requirements::themedCSS('bootstrap.min');
Requirements::javascript(THIRD_PARTY_PATH . 'javascript/chosen.jquery.min.js');
}
}
public function overview() {
if(!$this->canViewPage()) {
return $this->redirect(ToursPage::getFirstPageLink());
}
return array();
}
public function details() {
if(!$this->canViewPage()) {
return $this->redirect(ToursPage::getFirstPageLink());
}
// Have to return something...
return array(
'MetaTitle' => $this->resolveMetaTitle('MetaTitleDetails'),
'Photos' => $this->TourPhotos(),
'MetaImage' => $this->resolveMetaImageDetails(),
'MetaVideo' => false
);
}
public function reviews() {
if(!$this->canViewPage()) {
return $this->redirect(ToursPage::getFirstPageLink());
}
return array(
'MetaTitle' => $this->resolveMetaTitle('MetaTitleReviews'),
'Reviews' => $this->data()->Reviews()->Filter('Disabled', 0),
'MetaImage' => $this->resolveMetaImageReviews(),
'MetaVideo' => false
);
}
public function book() {
// If we don't book, then head to the contact page
if($this->ContactFormToBook) {
FlashMessage::add('Please use the Contact Us form if you\'d like to book a ' . $this->Title, 'success');
return $this->redirect(ContactPage::getFirstPageLink());
}
return $this->redirect(BookingPage::getFirstPageLink() . '?Tour=' . $this->ID);
}
/*
* brochure page - with a check to ensure that they are allowed to view the page
*/
public function brochure() {
if(!$this->canViewPage()) {
return $this->redirect(ToursPage::getFirstPageLink());
}
return array();
}
public function map(SS_HTTPRequest $request) {
if(!$this->isAllowedMap()) {
return $this->redirect($this->Link());
}
Requirements::javascript('https://maps.googleapis.com/maps/api/js?v=3.exp&signed_in=true&key=AIzaSyCNCBX_mLK0uuDElVttJVJgM2fuXIynv6E');
Requirements::javascript(THIRD_PARTY_PATH . 'javascript/map.js');
return array(
'MetaImage' => $this->resolveMetaImageMap(),
'MetaVideo' => false
);
}
}
That module provides the ability to include custom routes as described in the docs.
Example below:
GoogleSitemap::register_routes(array(
'/my-custom-controller/',
'/Security/',
'/Security/login/'
));
I am trying to get content from GitHub (using octonode) recursively. Content is fetched recursively but the final result of the entry promise is undefined:
The function to fetch the content:
_getRepoContent ( user, repo, ref, path ) {
return new Promise( ( resolved, rejected ) => {
this._client.repo( user + "/" + repo, ref ).contents( path, ( err, data ) => {
if ( err ) {
rejected( err );
} else {
resolved( data );
}
} );
} );
}
The main recursive function:
getContentRec ( sourceDef, data ) {
var results = data || [];
return this._getRepoContent( sourceDef.user, sourceDef.repo, sourceDef.ref, sourceDef.path )
.then( ( data ) => {
let dirs = [];
results = results.concat( data );
data.forEach( ( file ) => {
if ( file.type === "dir" ) {
dirs.push( file );
}
} );
if ( dirs.length > 0 ) {
let promises = [];
dirs.forEach( ( dir ) => {
let def = {
user: sourceDef.user,
repo: sourceDef.repo,
ref: sourceDef.ref,
path: dir.path
};
promises.push( this.getContentRec( def, results ) );
} );
return Promise.all( promises );
} else {
return Promise.resolve( results );
}
} )
.catch( ( err ) => {
return Promise.reject( err );
} );
}
Calling it:
getContentRec(
{
user: "nodejs",
repo: "node"
}
}).then( (data) => {
// data is undefined
})
Any ideas? Thx a lot in advance!
Solved the problem, here's the solution ...:
getContentRec ( sourceDef ) {
return this._getRepoContent( sourceDef.user, sourceDef.repo, sourceDef.ref, sourceDef.path )
.map( ( content ) => {
let def = {
user: sourceDef.user,
repo: sourceDef.repo,
ref: sourceDef.ref,
path: content.path
};
return content.type === "dir" ? this.getContentRec( def ) : content;
} )
.reduce( ( a, b ) => {
return a.concat( b )
}, [] );
}
Note:
This solution is using bluebird and not pure ES6 promises.