Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add user_select field #614

Merged
merged 14 commits into from
Jun 7, 2016
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Shortcake (Shortcode UI) #
**Contributors:** fusionengineering, mattheu, danielbachhuber, zebulonj, goldenapples, jitendraharpalani, sanchothefat, bfintal, davisshaver, garyj, mte90, fredserva, khromov, bronsonquick
**Tags:** shortcodes
**Requires at least:** 4.1
**Tested up to:** 4.4
**Contributors:** fusionengineering, mattheu, danielbachhuber, zebulonj, goldenapples, jitendraharpalani, sanchothefat, bfintal, davisshaver, garyj, mte90, fredserva, khromov, bronsonquick, dashaluna
**Tags:** shortcodes
**Requires at least:** 4.1
**Tested up to:** 4.4
**Stable tag:** 0.6.3
**License:** GPLv2 or later
**License URI:** http://www.gnu.org/licenses/gpl-2.0.html
**License:** GPLv2 or later
**License URI:** http://www.gnu.org/licenses/gpl-2.0.html

Shortcake makes using WordPress shortcodes a piece of cake.

Expand Down Expand Up @@ -60,6 +60,9 @@ We've removed the compatibility shim for the magical `content` attribute. If you
## Changelog ##


### 0.6.4 (June 1, 2016) ###
* Introduced a `user_select` input type for user selection.

### 0.6.3 (May 19, 2016) ###
* Introduced a `term_select` input type for taxonomy selection.

Expand Down
23 changes: 20 additions & 3 deletions dev.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ function shortcode_ui_dev_advanced_example() {
'taxonomy' => 'post_tag',
'multiple' => true,
),
array(
'label' => __( 'User Select', 'shortcode-ui-example' ),
'attr' => 'users',
'type' => 'user_select',
'multiple' => true,
),
array(
'label' => esc_html__( 'Color', 'shortcode-ui-example' ),
'attr' => 'color',
Expand Down Expand Up @@ -287,13 +293,12 @@ function shortcode_ui_dev_shortcode( $attr, $content, $shortcode_tag ) {
'attachment' => 0,
'page' => '',
'term' => '',
'users' => '',
'color' => '',
'alignment' => '',
'year' => '',
), $attr, $shortcode_tag );

// Make some sense of the data.

$attr['page'] = array_map(
function( $post_id ) {
return get_the_title( $post_id );
Expand All @@ -303,12 +308,20 @@ function( $post_id ) {

$attr['term'] = array_map(
function( $term_id ) {
$data = get_term( $term_id, 'category' );
$data = get_term( $term_id, 'post_tag' );
return $data->name;
},
array_filter( array_map( 'absint', explode( ',', $attr['term'] ) ) )
);

$attr['users'] = array_map(
function( $user_id ) {
$data = get_userdata( $user_id );
return $data->display_name;
},
array_filter( array_map( 'absint', explode( ',', $attr['users'] ) ) )
);

$attr['color'] = urldecode( $attr['color'] );

// Shortcode callbacks must return content, hence, output buffering here.
Expand Down Expand Up @@ -337,6 +350,10 @@ function( $term_id ) {
<b><?php esc_html_e( 'Terms:', 'shortcode-ui-example' ); ?></b> <?php echo esc_html( implode( ', ', $attr['term'] ) ); ?></br>
<?php endif; ?>

<?php if ( ! empty( $attr['users'] ) ) : ?>
<b><?php esc_html_e( 'Users:', 'shortcode-ui-example' ); ?></b> <?php echo esc_html( implode( ', ', $attr['users'] ) ); ?></br>
<?php endif; ?>

<?php if ( ! empty( $attr['color'] ) ) : ?>
<b><?php esc_html_e( 'Color:', 'shortcode-ui-example' ); ?></b> <span style="display: inline-block; width: 1.5em; height: 1.5em; vertical-align: bottom; background-color: <?php echo esc_html( $attr['color'] ); ?>"></span></br>
<?php endif; ?>
Expand Down
174 changes: 174 additions & 0 deletions inc/fields/class-field-user-select.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<?php

class Shortcode_UI_Field_User_Select {

private static $instance;

// All registered user fields.
private $user_fields = array();

// Field Settings.
private $fields = array(
'user_select' => array(
'template' => 'shortcode-ui-field-user-select',
'view' => 'editAttributeFieldUserSelect',
),
);

/**
* Setup the instance.
*
* @return Shortcode_UI_Field_User_Select
*/
public static function get_instance() {

if ( ! isset( self::$instance ) ) {
self::$instance = new self;
self::$instance->setup_actions();
}
return self::$instance;
}

/**
* Add the required actions and filters.
*/
private function setup_actions() {

add_filter( 'shortcode_ui_fields', array( $this, 'filter_shortcode_ui_fields' ) );
add_action( 'enqueue_shortcode_ui', array( $this, 'action_enqueue_shortcode_ui' ) );
add_action( 'wp_ajax_shortcode_ui_user_field', array( $this, 'action_wp_ajax_shortcode_ui_user_field' ) );
add_action( 'shortcode_ui_loaded_editor', array( $this, 'action_shortcode_ui_loaded_editor' ) );
}

/**
* Add our field to the shortcode fields.
*
* @param $fields
*
* @return array
*/
public function filter_shortcode_ui_fields( $fields ) {

return array_merge( $fields, $this->fields );
}

/**
* Add Select2 for our UI.
*/
public function action_enqueue_shortcode_ui() {

$plugin_dir = dirname( dirname( __FILE__ ) );

wp_enqueue_script( 'select2', plugins_url( 'lib/select2/select2.min.js', $plugin_dir ), array( 'jquery', 'jquery-ui-sortable' ), '3.5.2' );
wp_enqueue_style( 'select2', plugins_url( 'lib/select2/select2.css', $plugin_dir ), null, '3.5.2' );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should select2 be registered (e.g. via wp_register_script()) once and then just the handle enqueued here?

Here's how I've been doing it for a Customizer control that uses Select2:
https://github.com/xwp/wp-customize-object-selector/blob/423e8f62a326ba05f164b0077ce944ad2ed91bfc/php/class-plugin.php#L70-L76
https://github.com/xwp/wp-customize-object-selector/blob/423e8f62a326ba05f164b0077ce944ad2ed91bfc/php/class-plugin.php#L106-L112


wp_localize_script( 'shortcode-ui', 'shortcodeUiUserFieldData', array(
'nonce' => wp_create_nonce( 'shortcode_ui_field_user_select' ),
) );
}

/**
* Output styles and templates used by user select field.
*/
public function action_shortcode_ui_loaded_editor() {
?>

<style>

.edit-shortcode-form .select2-container {
min-width: 300px;
}

.edit-shortcode-form .select2-container a {
transition: none;
-webkit-transition: none;
}

.wp-admin .select2-drop {
z-index: 160001;
}

</style>

<script type="text/html" id="tmpl-shortcode-ui-field-user-select">
<div class="field-block shortcode-ui-field-user-select shortcode-ui-attribute-{{ data.attr }}">
<label for="{{ data.id }}">{{{ data.label }}}</label>
<input type="text" name="{{ data.attr }}" id="{{ data.id }}" value="{{ data.value }}" class="shortcode-ui-user-select" />
<# if ( typeof data.description == 'string' ) { #>
<p class="description">{{{ data.description }}}</p>
<# } #>
</div>
</script>

<?php
}

/**
* Ajax handler for select2 user field queries.
* Output JSON containing user data.
* Requires that shortcode, attr and nonce are passed.
* Requires that the field has been correctly registered and can be found in $this->post_fields
* Supports passing page number and search query string.
*
* @return null
*/
public function action_wp_ajax_shortcode_ui_user_field() {

$nonce = isset( $_GET['nonce'] ) ? sanitize_text_field( $_GET['nonce'] ) : null;
$requested_shortcode = isset( $_GET['shortcode'] ) ? sanitize_text_field( $_GET['shortcode'] ) : null;
$requested_attr = isset( $_GET['attr'] ) ? sanitize_text_field( $_GET['attr'] ) : null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each of the $_GET references should have wp_unslash() before any sanitization is done.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@westonruter could you explain a bit more why we need wp_unslash(). Still learning :) Thank you!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dashaluna sure, WordPress has an unfortunately legacy back-compat feature where it will force all of the input vars like $_GET and $_POST to be “slashed”. So for example, if you put in a search for “I'm happy”, then if you look at $_GET['s'] it will actually be I\'m happy.

For more information, see https://core.trac.wordpress.org/ticket/18322

$response = array( 'users' => array(), 'found_users' => 0, 'users_per_page' => 0 );

if ( ! wp_verify_nonce( $nonce, 'shortcode_ui_field_user_select' ) ) {
wp_send_json_error( $response );
}

$shortcodes = Shortcode_UI::get_instance()->get_shortcodes();

// Shortcode not found.
if ( ! isset( $shortcodes[ $requested_shortcode ] ) ) {
wp_send_json_error( $response );
}

$shortcode = $shortcodes[ $requested_shortcode ];

// Defaults user query args.
$query_args['search_columns'] = array( 'ID', 'user_login', 'user_nicename', 'user_email' );
$query_args['number'] = 10;

// Include selected users.
if ( isset( $_GET['include'] ) ) {
$query_args['include'] = is_array( $_GET['include'] ) ? $_GET['include'] : explode( ',', $_GET['include'] );
$query_args['include'] = array_map( 'absint', $query_args['include'] );
}

// Supports WP_User_Query query args.
foreach ( $shortcode['attrs'] as $attr ) {
if ( $attr['attr'] === $requested_attr && isset( $attr['query'] ) ) {
$query_args = $attr['query'];
}
}

if ( isset( $_GET['page'] ) ) {
$query_args['paged'] = sanitize_text_field( $_GET['page'] );
}

if ( ! empty( $_GET['s'] ) ) {
$query_args['search'] = '*' . sanitize_text_field( $_GET['s'] ) . '*';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$_GET['s'] needs wp_unslash()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest moving all of the accessing of the input vars to the top of this method and to store the sanitized values in regular variables.

So at the top:

$s = null;
if ( isset( $_GET['s'] ) ) {
    $s = sanitize_text_field( wp_unslash( $_GET['s'] ) );
}

And then down here:

if ( ! empty( $_GET['s'] ) ) {
    $query_args['search'] = '*' . $s . '*';
}

I think it is a best practice to sanitize early and escape late.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@westonruter in the second code chunk you mean if ( ! empty( $s ) ) { as it can be non-empty before sanitisation and empty after?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, sorry. It could be just:

if ( $s ) {
    $query_args['search'] = '*' . $s . '*';
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Np, just making sure I understand it correctly :)

}

$query = new WP_User_Query( $query_args );

foreach ( $query->get_results() as $user ) {
array_push( $response['users'], array(
'id' => $user->ID,
'text' => html_entity_decode( $user->display_name ),
) );
}

$response['found_users'] = $query->get_total();
$response['users_per_page'] = $query->query_vars['number'];

wp_send_json_success( $response );
}
}