diff --git a/.github/hookdoc-tmpl/README.md b/.github/hookdoc-tmpl/README.md new file mode 100644 index 00000000..4481cbad --- /dev/null +++ b/.github/hookdoc-tmpl/README.md @@ -0,0 +1,7 @@ +# Welcome to the Ultimate Member Plugin Hook Documentation + +This resource is generated documentation on actions and filters found in the [Ultimate Member](https://wordpress.org/plugins/ultimate-member/) plugin. Use the sidebar to browse and navigate. + +For more information about using Ultimate Member with WordPress, please see the [Ultimate Member Docs](https://docs.ultimatemember.com/). + +To report an issue with Ultimate Member, please visit the [Support forum](https://wordpress.org/support/plugin/ultimate-member/). diff --git a/.github/hookdoc-tmpl/layout.tmpl b/.github/hookdoc-tmpl/layout.tmpl new file mode 100644 index 00000000..411c75fa --- /dev/null +++ b/.github/hookdoc-tmpl/layout.tmpl @@ -0,0 +1,46 @@ + + + + + <?js= title ?> - Ultimate Member Hook Docs + + + + + + + + + + + + class="home"> + +
+ + +

+ + + + + + + +
+ + + +
+ + + + + diff --git a/.github/hookdoc-tmpl/static/styles-um.css b/.github/hookdoc-tmpl/static/styles-um.css new file mode 100644 index 00000000..2d2a06a4 --- /dev/null +++ b/.github/hookdoc-tmpl/static/styles-um.css @@ -0,0 +1,99 @@ +body { + background: #fefefe; + color: #232323; + font-family: 'IBM Plex Sans', sans-serif; + font-size: 1.3rem; + font-weight: 300; +} + +h1, h2, h3 { + line-height: 1.2; + font-family: 'Playfair Display', sans-serif; + font-weight: 900; + letter-spacing: -.01em; +} + +h1.page-title { + font-size: 42px; + margin-top: .5em; +} + +nav ul { + font-size: 1.2rem; +} + +nav li a { + background-image: none; +} + +nav li a:hover { + text-decoration: underline; +} + +code, pre, +nav ul a, nav ul a:visited, nav ul a:active, +.name, .signature, +.params .name, .props .name, +.name code { + font-family: 'IBM Plex Mono', monospace; +} + +article h1 { + margin: 12px 0 32px; +} + +a { + background-image: linear-gradient(transparent calc(100% - 7px), #f2dede 0), + linear-gradient(transparent calc(100% - 7px), #cef8f7 0); + background-position: 0 0; + background-repeat: no-repeat; + background-size: 0 100%, 100% 100%; + color: #232323; + text-decoration: none; + transition: all .1s; +} + +a:visited, +a:active { + color: #232323; +} + +a:focus, +a:hover { + background-size: 100% 100%, 100% 100%; + color: #232323; + text-decoration: none; +} + +a.banner { + background-image: none; + margin-left: -10px; +} + +a.banner img { + width: 100%; + max-width: 888px; +} + +footer { + text-align: center; + font-size: .8em; + font-style: normal; + font-weight: 300; +} + +.home #main > section:first-of-type, +.home nav > h2 { + display: none; +} + +.prettyprint.source { + font-size: 14px; +} + +.prettyprint code { + font-size: 16px; + padding: 2px 10px; + line-height: 18px; + max-height: 150px; +} diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml new file mode 100644 index 00000000..05910f04 --- /dev/null +++ b/.github/workflows/build-docs.yml @@ -0,0 +1,31 @@ +name: Build Hook Docs + +on: + push: + branches: + - master + +jobs: + hookdocs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 16 + - name: npm install, and build docs + run: | + npm install + npm run build:docs + - name: Install dependencies + run: composer install --prefer-dist --no-progress --no-suggest + - name: PhpDocumentor + uses: gpupo/actions-phpDocumentor@1.0 + with: + dir: './' + - name: Deploy to GH Pages + uses: JamesIves/github-pages-deploy-action@v4.3.3 + with: + branch: gh-pages # The branch the action should deploy to. + folder: 'docs/' # The folder the action should deploy. + token: ${{ secrets.GH_PAT }} diff --git a/README.md b/README.md index 3d77ce69..1287a4e4 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ GNU Version 2 or Any Later Version ## Releases -[Official Release Version: 2.6.2](https://github.com/ultimatemember/ultimatemember/releases/tag/2.6.2). +[Official Release Version: 2.6.3](https://github.com/ultimatemember/ultimatemember/releases/tag/2.6.3). ## Changelog diff --git a/composer.json b/composer.json index e309c5c1..9b5aae8f 100644 --- a/composer.json +++ b/composer.json @@ -6,6 +6,12 @@ "license": "GPL-3.0-or-later", "minimum-stability": "stable", "prefer-stable": true, + "config": { + "allow-plugins": { + "symfony/flex": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, "require": { "ext-mbstring": "*", "ext-json": "*", @@ -18,7 +24,7 @@ "woocommerce/action-scheduler": "3.2.1" }, "require-dev": { - "roave/security-advisories": "dev-master", + "roave/security-advisories": "dev-latest", "wp-cli/wp-cli": "*", "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1", "object-calisthenics/phpcs-calisthenics-rules": "*", diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..a0dfe10e --- /dev/null +++ b/docs/README.md @@ -0,0 +1,11 @@ +# Welcome to the Ultimate Member Documentation + +There are GitHub Pages documentation of [Ultimate Member](https://wordpress.org/plugins/ultimate-member/) plugin. Use the links below: + +The generated documentation on actions and filters is [here](https://ultimatemember.github.io/ultimatemember/hooks). + +The generated documentation on PHP namespaces, classes, properties, methods and functions is [here](https://ultimatemember.github.io/ultimatemember/phpdoc). + +For more information about using Ultimate Member with WordPress, please see the [Ultimate Member Docs](https://docs.ultimatemember.com/). + +To report an issue with Ultimate Member, please visit the [Support forum](https://wordpress.org/support/plugin/ultimate-member/). diff --git a/hookdoc-conf.json b/hookdoc-conf.json new file mode 100644 index 00000000..5e3a6944 --- /dev/null +++ b/hookdoc-conf.json @@ -0,0 +1,31 @@ +{ + "opts": { + "destination": "docs/hooks", + "template": "node_modules/wp-hookdoc/template", + "recurse": true, + "readme": "./.github/hookdoc-tmpl/README.md" + }, + "source": { + "include": [ + "./" + ], + "exclude": [ + "node_modules" + ], + "includePattern": ".+\\.(php)?$" + }, + "plugins": [ + "node_modules/wp-hookdoc/plugin", + "plugins/markdown" + ], + "templates": { + "default": { + "layoutFile": "./.github/hookdoc-tmpl/layout.tmpl", + "staticFiles": { + "include": [ + "./.github/hookdoc-tmpl/static" + ] + } + } + } +} diff --git a/includes/admin/assets/css/um-admin-forms.css b/includes/admin/assets/css/um-admin-forms.css index 36692707..6beb8ecf 100644 --- a/includes/admin/assets/css/um-admin-forms.css +++ b/includes/admin/assets/css/um-admin-forms.css @@ -146,6 +146,14 @@ textarea.um-forms-field.um-small-field { flex: 0.9; } +.um-multi-selects-option-line .um-field-wrapper.um-custom-order-fields label select{ + width: auto; + display: inline; + font-weight: normal; + max-width: 70%; + flex: 0.9; +} + .um-multi-selects-option-line.um-admin-drag-fld .um-field-wrapper { width: calc( 100% - 90px ); } diff --git a/includes/admin/assets/js/um-admin-forms.js b/includes/admin/assets/js/um-admin-forms.js index 314d6d24..5bd8e40f 100644 --- a/includes/admin/assets/js/um-admin-forms.js +++ b/includes/admin/assets/js/um-admin-forms.js @@ -1,17 +1,48 @@ function um_admin_init_users_select() { if ( jQuery('.um-user-select-field').length ) { + function avatarformat( data ) { + var option; + if ( ! data.id ) { + return data.text; + } + if ( 'undefined' !== typeof data.img ) { + option = jQuery(' ' + data.text + ''); + } else { + var img = data.element.attributes['data-img']['value']; + if ( img ) { + option = jQuery(' ' + data.text + ''); + } else { + option = jQuery('' + data.text + ''); + } + } + return option; + } + var select2_atts = { ajax: { url: wp.ajax.settings.url, dataType: 'json', delay: 250, // delay in ms while typing when to perform a AJAX search data: function( params ) { - return { - search: params.term, // search query + var args = { action: 'um_get_users', // AJAX action for admin-ajax.php + search: params.term, // search query page: params.page || 1, // infinite scroll pagination nonce: um_admin_scripts.nonce }; + + jQuery.each( jQuery(this)[0].attributes, function() { + // this.attributes is not a plain object, but an array + // of attribute nodes, which contain both the name and value + if ( this.specified ) { + if ( -1 !== this.name.indexOf( 'data-ajax-args-' ) ) { + var arg_name = this.name.replace( 'data-ajax-args-', '' ).trim(); + args[ arg_name ] = this.value; + } + } + }); + + return args; }, processResults: function( response, params ) { params.page = params.page || 1; @@ -19,7 +50,11 @@ function um_admin_init_users_select() { if ( response.data.users ) { jQuery.each( response.data.users, function( index, text ) { - options.push( { id: text.ID, text: text.user_login + ' (#' + text.ID + ')' } ); + if ( typeof text.img !== 'undefined' ) { + options.push({ id: text.ID, text: text.user_login + ' (#' + text.ID + ')', img: text.img }); + } else { + options.push( { id: text.ID, text: text.user_login + ' (#' + text.ID + ')' } ); + } }); } @@ -38,7 +73,9 @@ function um_admin_init_users_select() { allowHtml: true, dropdownCssClass: 'um-select2-users-dropdown', containerCssClass : 'um-select2-users-container', - placeholder: jQuery(this).data('placeholder') + placeholder: jQuery(this).data('placeholder'), + templateSelection: avatarformat, + templateResult: avatarformat }; jQuery('.um-user-select-field').select2( select2_atts ); @@ -245,11 +282,23 @@ jQuery(document).ready( function() { html += ''; } + let dataTypesOptions = ''; + jQuery.each( um_forms_data.md_sorting_data_types, function( key, label ) { + dataTypesOptions += ''; + } ); + html += '' + selector_html + '' + '' + '' + wp.i18n.__( 'Remove', 'ultimate-member' ) + '' + '' + '' + + '' + + '' + '' + ''; list.append( html ); @@ -259,6 +308,8 @@ jQuery(document).ready( function() { jQuery( '#' + list.data('id_attr') + '-' + k ).parents('li').find('.um-field-wrapper.um-custom-order-fields input[name="meta_key"]').attr('name', 'um_metadata[_um_sorting_fields][other_data][' + k + '][meta_key]'); jQuery( '#' + list.data('id_attr') + '-' + k ).parents('li').find('.um-field-wrapper.um-custom-order-fields input[name="label"]').attr('name', 'um_metadata[_um_sorting_fields][other_data][' + k + '][label]'); + jQuery( '#' + list.data('id_attr') + '-' + k ).parents('li').find('.um-field-wrapper.um-custom-order-fields select[name="data_type"]').attr('name', 'um_metadata[_um_sorting_fields][other_data][' + k + '][data_type]'); + jQuery( '#' + list.data('id_attr') + '-' + k ).parents('li').find('.um-field-wrapper.um-custom-order-fields select[name="order"]').attr('name', 'um_metadata[_um_sorting_fields][other_data][' + k + '][order]'); }); diff --git a/includes/admin/class-admin.php b/includes/admin/class-admin.php index e9b5ed80..7a848412 100644 --- a/includes/admin/class-admin.php +++ b/includes/admin/class-admin.php @@ -1,21 +1,18 @@ array( 'sanitize' => 'text', ), + '_um_sortby_custom_type' => array( + 'sanitize' => 'text', + ), + '_um_sortby_custom_order' => array( + 'sanitize' => 'text', + ), '_um_enable_sorting' => array( 'sanitize' => 'bool', ), @@ -867,6 +870,12 @@ if ( ! class_exists( 'um\admin\Admin' ) ) { if ( isset( $item['label'] ) ) { $item['label'] = sanitize_text_field( $item['label'] ); } + if ( isset( $item['order'] ) ) { + $item['order'] = sanitize_text_field( $item['order'] ); + } + if ( isset( $item['data_type'] ) ) { + $item['data_type'] = sanitize_text_field( $item['data_type'] ); + } return $item; } else { diff --git a/includes/admin/core/class-admin-dragdrop.php b/includes/admin/core/class-admin-dragdrop.php index 18504e58..b135b91c 100644 --- a/includes/admin/core/class-admin-dragdrop.php +++ b/includes/admin/core/class-admin-dragdrop.php @@ -1,30 +1,37 @@ admin()->check_ajax_nonce(); @@ -153,12 +160,10 @@ if ( ! class_exists( 'um\admin\core\Admin_DragDrop' ) ) { update_option( 'um_form_rowdata_' . $form_id, $this->row_data ); UM()->query()->update_attr( 'custom_fields', $form_id, $fields ); - } - /** - * Load form to maintain form order + * Load form to maintain form order. */ public function load_field_order() { @@ -233,7 +238,7 @@ if ( ! class_exists( 'um\admin\core\Admin_DragDrop' ) ) { - +
@@ -244,6 +249,5 @@ if ( ! class_exists( 'um\admin\core\Admin_DragDrop' ) ) { UM()->member_directory()->sort_data_types, ) ); @@ -566,7 +567,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Enqueue' ) ) { * Load Gutenberg scripts */ public function load_gutenberg_js() { - //disable Gutenberg scripts to avoid the conflicts + /** This filter is documented in includes/core/class-blocks.php */ $disable_script = apply_filters( 'um_disable_blocks_script', false ); if ( $disable_script ) { return; diff --git a/includes/admin/core/class-admin-forms.php b/includes/admin/core/class-admin-forms.php index 260d29a9..92355732 100644 --- a/includes/admin/core/class-admin-forms.php +++ b/includes/admin/core/class-admin-forms.php @@ -125,6 +125,9 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms' ) ) { 'target' => array(), 'class' => array(), ), + 'button' => array( + 'class' => array(), + ), 'i' => array( 'class' => array(), ), @@ -576,33 +579,39 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms' ) ) { * * @return bool|string */ - function render_users_dropdown( $field_data ) { + public function render_users_dropdown( $field_data ) { if ( empty( $field_data['id'] ) ) { return false; } $multiple = ! empty( $field_data['multi'] ) ? 'multiple' : ''; - $id = ( ! empty( $this->form_data['prefix_id'] ) ? $this->form_data['prefix_id'] : '' ) . '_' . $field_data['id']; + $id = ( ! empty( $this->form_data['prefix_id'] ) ? $this->form_data['prefix_id'] : '' ) . '_' . $field_data['id']; $id_attr = ' id="' . esc_attr( $id ) . '" '; - $class = ! empty( $field_data['class'] ) ? $field_data['class'] . ' ' : ' '; - $class .= ! empty( $field_data['size'] ) ? 'um-' . $field_data['size'] . '-field' : 'um-long-field'; + $class = ! empty( $field_data['class'] ) ? $field_data['class'] . ' ' : ' '; + $class .= ! empty( $field_data['size'] ) ? 'um-' . $field_data['size'] . '-field' : 'um-long-field'; $class_attr = ' class="um-forms-field um-user-select-field' . esc_attr( $class ) . '" '; $data = array( 'field_id' => $field_data['id'], + 'avatar' => ! empty( $field_data['avatar'] ) ? 1 : 0, ); + if ( ! empty( $field_data['data'] ) && is_array( $field_data['data'] ) ) { + $data = array_merge( $data, $field_data['data'] ); + } + $data_attr = ''; foreach ( $data as $key => $value ) { $data_attr .= ' data-' . $key . '="' . esc_attr( $value ) . '" '; } - $name = $field_data['id']; - $name = ! empty( $this->form_data['prefix_id'] ) ? $this->form_data['prefix_id'] . '[' . $name . ']' : $name; + $name = $field_data['id']; + $name = ! empty( $this->form_data['prefix_id'] ) ? $this->form_data['prefix_id'] . '[' . $name . ']' : $name; $hidden_name_attr = ' name="' . $name . '" '; - $name = $name . ( ! empty( $field_data['multi'] ) ? '[]' : '' ); + + $name = $name . ( ! empty( $field_data['multi'] ) ? '[]' : '' ); $name_attr = ' name="' . $name . '" '; $value = $this->get_field_value( $field_data ); @@ -620,15 +629,22 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms' ) ) { $options = ''; if ( ! empty( $users ) ) { foreach ( $users as $user ) { - $options .= ''; + if ( ! empty( $field_data['avatar'] ) ) { + $url = get_avatar_url( $user->ID, 'size=20' ); + $options .= ''; + } else { + $options .= ''; + } } } + $placeholder = ! empty( $field_data['placeholder'] ) ? $field_data['placeholder'] : __( 'Select Users', 'ultimate-member' ); + $hidden = ''; if ( ! empty( $multiple ) ) { $hidden = ""; } - $html = "$hidden"; + $html = "$hidden"; return $html; } @@ -869,8 +885,11 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms' ) ) { $value = $this->get_field_value( $field_data ); + add_filter( 'wp_default_editor', array( &$this, 'set_default_editor_fix' ) ); + ob_start(); - wp_editor( $value, + wp_editor( + $value, $id, array( 'textarea_name' => $name, @@ -878,14 +897,25 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms' ) ) { 'editor_height' => 425, 'wpautop' => false, 'media_buttons' => false, - 'editor_class' => $class + 'editor_class' => $class, ) ); $html = ob_get_clean(); + + remove_filter( 'wp_default_editor', array( &$this, 'set_default_editor_fix' ) ); + return $html; } + /** + * Fix the displaying wp_editor on macOS + * + * @return string + */ + public function set_default_editor_fix() { + return 'html'; + } /** * @param $field_data @@ -1590,16 +1620,30 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms' ) ) { if ( ! empty( $values ) && is_array( $values ) ) { foreach ( $values as $k => $value ) { - $other_key = ''; + $other_key = ''; $other_label = ''; + $other_type = ''; + $other_order = ''; + if ( is_array( $value ) ) { - $keys = array_keys( $value ); + $keys = array_keys( $value ); $other_key = $keys[0]; - $labels = array_values( $value ); - $other_label = $labels[0]; + if ( ! empty( $value['label'] ) ) { + $other_label = $value['label']; + } else { + $labels = array_values( $value ); + $other_label = $labels[0]; + } + + if ( ! empty( $value['type'] ) ) { + $other_type = $value['type']; + } + if ( ! empty( $value['order'] ) ) { + $other_order = $value['order']; + } } else { - if ( ! in_array( $value, array_keys( $field_data['options'] ) ) ) { + if ( ! array_key_exists( $value, $field_data['options'] ) ) { continue; } } @@ -1609,7 +1653,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms' ) ) { $options = ''; foreach ( $field_data['options'] as $key => $option ) { if ( is_array( $value ) ) { - $selected = selected( $key == 'other', true, false ); + $selected = selected( 'other' === $key, true, false ); } else { $selected = selected( $key == $value, true, false ); } @@ -1621,12 +1665,25 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms' ) ) { if ( $sorting ) { $html .= ''; } - $html .= " - - " . __( 'Remove', 'ultimate-member' ) . " - - - "; + + $data_types_html = ''; + foreach ( UM()->member_directory()->sort_data_types as $type_key => $type_label ) { + $data_types_html .= ''; + } + + $html .= ' + + ' . __( 'Remove', 'ultimate-member' ) . ' + + + + + '; } } elseif ( ! empty( $field_data['show_default_number'] ) && is_numeric( $field_data['show_default_number'] ) && $field_data['show_default_number'] > 0 ) { $i = 0; diff --git a/includes/admin/core/class-admin-navmenu.php b/includes/admin/core/class-admin-navmenu.php index bb0dcd4f..d2eba992 100644 --- a/includes/admin/core/class-admin-navmenu.php +++ b/includes/admin/core/class-admin-navmenu.php @@ -47,16 +47,13 @@ if ( ! class_exists( 'um\admin\core\Admin_Navmenu' ) ) { * Fires just before the move buttons of a nav menu item in the menu editor. * Adds block "Ultimate Member Menu Settings" * - * @since WP 5.4.0 - * @hook wp_nav_menu_item_custom_fields - * - * @param int $item_id Menu item ID. + * @param int $item_id Menu item ID. * @param \WP_Post $item Menu item data object. - * @param int $depth Depth of menu item. Used for padding. + * @param int $depth Depth of menu item. Used for padding. * @param \stdClass $args An object of menu item arguments. - * @param int $id Nav menu ID. + * @param int $id Nav menu ID. */ - function wp_nav_menu_item_custom_fields( $item_id, $item, $depth, $args, $id = null ) { + public function wp_nav_menu_item_custom_fields( $item_id, $item, $depth, $args, $id = null ) { $um_nav_public = get_post_meta( $item->ID, 'menu-item-um_nav_public', true ); $_nav_roles_meta = get_post_meta( $item->ID, 'menu-item-um_nav_roles', true ); diff --git a/includes/admin/core/class-admin-users.php b/includes/admin/core/class-admin-users.php index 09cef460..e8c7c296 100644 --- a/includes/admin/core/class-admin-users.php +++ b/includes/admin/core/class-admin-users.php @@ -1,13 +1,12 @@ admin()->check_ajax_nonce(); $search_request = ! empty( $_REQUEST['search'] ) ? sanitize_text_field( $_REQUEST['search'] ) : ''; @@ -53,17 +51,26 @@ if ( ! class_exists( 'um\admin\core\Admin_Users' ) ) { $args = array( 'fields' => array( 'ID', 'user_login' ), 'paged' => $page, - 'number' => $per_page + 'number' => $per_page, ); if ( ! empty( $search_request ) ) { - $args['search'] = $search_request; + $args['search'] = '*' . $search_request . '*'; } + $args = apply_filters( 'um_get_users_list_ajax_args', $args ); + $users_query = new \WP_User_Query( $args ); $users = $users_query->get_results(); $total_count = $users_query->get_total(); + if ( ! empty( $_REQUEST['avatar'] ) ) { + foreach ( $users as $key => $user ) { + $url = get_avatar_url( $user->ID ); + $users[ $key ]->img = $url; + } + } + wp_send_json_success( array( 'users' => $users, @@ -72,7 +79,6 @@ if ( ! class_exists( 'um\admin\core\Admin_Users' ) ) { ); } - /** * Restrict the edit/delete users via wp-admin screen by the UM role capabilities * @@ -83,7 +89,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Users' ) ) { * * @return mixed */ - function map_caps_by_role( $allcaps, $cap, $args, $user ) { + public function map_caps_by_role( $allcaps, $cap, $args, $user ) { if ( isset( $cap[0] ) && $cap[0] == 'edit_users' ) { if ( isset( $args[0] ) && isset( $args[1] ) && ! user_can( $args[1], 'administrator' ) && $args[0] == 'edit_user' ) { if ( isset( $args[2] ) && ! UM()->roles()->um_current_user_can( 'edit', $args[2] ) ) { @@ -107,13 +113,12 @@ if ( ! class_exists( 'um\admin\core\Admin_Users' ) ) { return $allcaps; } - /** * Does an action to user asap * * @param string $action */ - function user_action_hook( $action ) { + public function user_action_hook( $action ) { switch ( $action ) { default: /** @@ -176,12 +181,12 @@ if ( ! class_exists( 'um\admin\core\Admin_Users' ) ) { } } - /** * Add UM Bulk actions to Users List Table * */ - function restrict_manage_users() { ?> + public function restrict_manage_users() { + ?>
@@ -197,16 +202,16 @@ if ( ! class_exists( 'um\admin\core\Admin_Users' ) ) { - ID; $actions['frontend_profile'] = '' . __( 'View profile', 'ultimate-member' ) . ''; @@ -312,14 +316,13 @@ if ( ! class_exists( 'um\admin\core\Admin_Users' ) ) { return $actions; } - /** * Change default sorting at WP Users list table * * @param array $args * @return array */ - function hide_by_caps( $args ) { + public function hide_by_caps( $args ) { if ( ! current_user_can( 'administrator' ) ) { $can_view_roles = um_user( 'can_view_roles' ); if ( um_user( 'can_view_all' ) && ! empty( $can_view_roles ) ) { @@ -330,7 +333,6 @@ if ( ! class_exists( 'um\admin\core\Admin_Users' ) ) { return $args; } - /** * Change default sorting at WP Users list table * @@ -350,7 +352,6 @@ if ( ! class_exists( 'um\admin\core\Admin_Users' ) ) { return $query; } - /** * Filter WP users by UM Status * @@ -385,7 +386,6 @@ if ( ! class_exists( 'um\admin\core\Admin_Users' ) ) { return $query; } - /** * Add status links to WP Users List Table * @@ -474,7 +474,6 @@ if ( ! class_exists( 'um\admin\core\Admin_Users' ) ) { return $views; } - /** * Bulk user editing actions */ @@ -561,14 +560,13 @@ if ( ! class_exists( 'um\admin\core\Admin_Users' ) ) { } } - /** * Sets redirect URI after bulk action * * @param string $uri * @return string */ - function set_redirect_uri( $uri ) { + public function set_redirect_uri( $uri ) { if ( ! empty( $_REQUEST['s'] ) ) { $uri = add_query_arg( 's', sanitize_text_field( $_REQUEST['s'] ), $uri ); @@ -581,6 +579,5 @@ if ( ! class_exists( 'um\admin\core\Admin_Users' ) ) { return $uri; } - } } diff --git a/includes/admin/core/list-tables/roles-list-table.php b/includes/admin/core/list-tables/roles-list-table.php index 1ac73f4d..743171c2 100644 --- a/includes/admin/core/list-tables/roles-list-table.php +++ b/includes/admin/core/list-tables/roles-list-table.php @@ -42,6 +42,23 @@ if ( isset( $_GET['action'] ) ) { } delete_option( "um_role_{$role_key}_meta" ); + /** + * Fires after delete UM role. + * + * @since 2.6.3 + * @hook um_after_delete_role + * + * @param {string} $role_key Role key. + * @param {array} $role_meta Role meta. + * + * @example Make any custom action after deleting UM role. + * function my_custom_um_after_delete_role( $role_key, $role_meta ) { + * // your code here + * } + * add_action( 'um_after_delete_role', 'my_custom_um_after_delete_role', 10, 2 ); + */ + do_action( 'um_after_delete_role', $role_key, $role_meta ); + $um_roles = array_diff( $um_roles, array( $role_key ) ); $roleID = 'um_' . $role_key; @@ -114,6 +131,23 @@ if ( isset( $_GET['action'] ) ) { } delete_option( "um_role_{$role_key}_meta" ); + + /** + * Fires after delete UM role meta. + * + * @since 2.6.3 + * @hook um_after_delete_role_meta + * + * @param {string} $role_key Role key. + * @param {array} $role_meta Role meta. + * + * @example Make any custom action after deleting UM role meta. + * function my_custom_um_after_delete_role_meta( $role_key, $role_meta ) { + * // your code here + * } + * add_action( 'um_after_delete_role_meta', 'my_custom_um_after_delete_role_meta', 10, 2 ); + */ + do_action( 'um_after_delete_role_meta', $role_key, $role_meta ); } um_js_redirect( add_query_arg( 'msg', 'reset', $redirect ) ); @@ -362,7 +396,25 @@ class UM_Roles_List_Table extends WP_List_Table { } } - return sprintf('%1$s %2$s', '' . stripslashes( $item['name'] ) . '', $this->row_actions( $actions ) ); + /** + * Filters the role actions in WP ListTable Ultimate Member > Roles screen. + * + * @since 2.6.3 + * @hook um_role_row_actions + * + * @param {array} $actions Action links. + * @param {string} $id Role key. + * + * @example Add custom action to role's row. + * function my_custom_um_role_row_actions( $actions, $id ) { + * $actions['{action_key}'] = "Action Title"; + * return $actions; + * } + * add_action( 'um_role_row_actions', 'my_custom_um_role_row_actions', 10, 2 ); + */ + $actions = apply_filters( 'um_role_row_actions', $actions, $id ); + + return sprintf( '%1$s %2$s', '' . stripslashes( $item['name'] ) . '', $this->row_actions( $actions ) ); } diff --git a/includes/admin/templates/directory/sorting.php b/includes/admin/templates/directory/sorting.php index b7f7832a..9a9566d0 100644 --- a/includes/admin/templates/directory/sorting.php +++ b/includes/admin/templates/directory/sorting.php @@ -24,6 +24,27 @@ $fields = array( 'value' => UM()->query()->get_meta_value( '_um_sortby_custom', null, 'na' ), 'conditional' => array( '_um_sortby', '=', 'other' ), ), + array( + 'id' => '_um_sortby_custom_type', + 'type' => 'select', + 'label' => __( 'Data type', 'ultimate-member' ), + 'tooltip' => __( 'To correct sort by a custom field, choose a data type', 'ultimate-member' ), + 'value' => UM()->query()->get_meta_value( '_um_sortby_custom_type', null, 'CHAR' ), + 'options' => UM()->member_directory()->sort_data_types, + 'conditional' => array( '_um_sortby', '=', 'other' ), + ), + array( + 'id' => '_um_sortby_custom_order', + 'type' => 'select', + 'label' => __( 'Order', 'ultimate-member' ), + 'tooltip' => __( 'To correct sort by a custom field, choose an order', 'ultimate-member' ), + 'value' => UM()->query()->get_meta_value( '_um_sortby_custom_order', null, 'ASC' ), + 'options' => array( + 'ASC' => __( 'ASC', 'ultimate-member' ), + 'DESC' => __( 'DESC', 'ultimate-member' ), + ), + 'conditional' => array( '_um_sortby', '=', 'other' ), + ), array( 'id' => '_um_sortby_custom_label', 'type' => 'text', diff --git a/includes/admin/templates/role/role-edit.php b/includes/admin/templates/role/role-edit.php index 36845ca5..175ba84f 100644 --- a/includes/admin/templates/role/role-edit.php +++ b/includes/admin/templates/role/role-edit.php @@ -1,4 +1,5 @@ -options()->update( 'custom_roles_increment', $auto_increment ); } + + $update = false; } - $role_meta = $data; + /** + * Filters the role meta before save it to DB. + * + * @param {array} $data Role meta. + * @param {string} $id Role key. + * @param {bool} $update Create or update role. "True" if update. + * + * @since 2.6.3 + * @hook um_role_edit_data + * + * @example Add custom metadata for role on saving. + * function my_custom_um_role_edit_data( $data, $id, $update ) { + * $data['{meta_key}'] = {meta_value}; // set your meta key and meta value + * return $data; + * } + * add_action( 'um_role_edit_data', 'my_custom_um_role_edit_data', 10, 3 ); + * @example Force remove role's metadata on saving when update. + * function my_custom_um_role_edit_data( $data, $id, $update ) { + * if ( true === $update ) { + * unset( $data['{meta_key}'] ); // set your meta key + * } + * return $data; + * } + * add_action( 'um_role_edit_data', 'my_custom_um_role_edit_data', 10, 3 ); + */ + $role_meta = apply_filters( 'um_role_edit_data', $data, $id, $update ); unset( $role_meta['id'] ); update_option( "um_role_{$id}_meta", $role_meta ); diff --git a/includes/class-config.php b/includes/class-config.php index 1892d6fe..3273fa67 100644 --- a/includes/class-config.php +++ b/includes/class-config.php @@ -1,13 +1,12 @@ It adds iframe HTML tag and 'onclick' attribute for strong tag. + * function add_extra_kses_allowed_tags( $allowed_html, $context ) { + * if ( 'templates' === $context ) { + * $allowed_html['iframe'] = array( + * 'src' => true, + * ); + * $allowed_html['strong']['onclick'] = true; + * } + * return $allowed_html; + * } + * add_filter( 'um_late_escaping_allowed_tags', 'add_extra_kses_allowed_tags', 10, 2 ); */ $allowed_html = apply_filters( 'um_late_escaping_allowed_tags', $allowed_html, $context ); diff --git a/includes/core/class-account.php b/includes/core/class-account.php index 446a5cb2..f8bea1b5 100644 --- a/includes/core/class-account.php +++ b/includes/core/class-account.php @@ -472,7 +472,7 @@ if ( ! class_exists( 'um\core\Account' ) ) { */ function tab_link( $id ) { - if ( get_option( 'permalink_structure' ) ) { + if ( UM()->is_permalinks ) { $url = trailingslashit( untrailingslashit( um_get_core_page( 'account' ) ) ); $url = $url . $id . '/'; diff --git a/includes/core/class-blocks.php b/includes/core/class-blocks.php index 583743fb..1c8eb590 100644 --- a/includes/core/class-blocks.php +++ b/includes/core/class-blocks.php @@ -1,31 +1,40 @@ options()->get( 'restricted_blocks' ); + if ( empty( $restricted_blocks ) ) { + return $settings; + } + if ( empty( $settings['attributes']['um_is_restrict'] ) ) { $settings['attributes']['um_is_restrict'] = array( 'type' => 'boolean', @@ -55,9 +64,31 @@ if ( ! class_exists( 'um\core\Blocks' ) ) { return $settings; } - + /** + * Register UM Blocks. + * + * @uses register_block_type_from_metadata() + */ public function block_editor_render() { - //disable Gutenberg scripts to avoid the conflicts + /** + * Filters the variable to disable adding UM Blocks to Gutenberg editor. + * + * Note: It's "false" by default. To disable Gutenberg scripts to avoid the conflicts set it to "true" + * + * @since 2.6.3 + * @hook um_disable_blocks_script + * + * @param {bool} $disable_script Disabling block scripts variable. + * + * @return {bool} It's true for disabling block scripts. + * + * @example Disable block scripts. + * function my_custom_um_disable_blocks_script( $disable_script ) { + * $disable_script = true; + * return $disable_script; + * } + * add_filter( 'um_disable_blocks_script', 'my_custom_um_disable_blocks_script', 10, 1 ); + */ $disable_script = apply_filters( 'um_disable_blocks_script', false ); if ( $disable_script ) { return; @@ -70,7 +101,7 @@ if ( ! class_exists( 'um\core\Blocks' ) ) { $blocks = array( 'um-block/um-member-directories' => array( - 'render_callback' => array( $this, 'um_member_directories_render' ), + 'render_callback' => array( $this, 'member_directories_render' ), 'attributes' => array( 'member_id' => array( 'type' => 'string', @@ -78,7 +109,7 @@ if ( ! class_exists( 'um\core\Blocks' ) ) { ), ), 'um-block/um-forms' => array( - 'render_callback' => array( $this, 'um_forms_render' ), + 'render_callback' => array( $this, 'forms_render' ), 'attributes' => array( 'form_id' => array( 'type' => 'string', @@ -86,10 +117,10 @@ if ( ! class_exists( 'um\core\Blocks' ) ) { ), ), 'um-block/um-password-reset' => array( - 'render_callback' => array( $this, 'um_password_reset_render' ), + 'render_callback' => array( $this, 'password_reset_render' ), ), 'um-block/um-account' => array( - 'render_callback' => array( $this, 'um_account_render' ), + 'render_callback' => array( $this, 'account_render' ), 'attributes' => array( 'tab' => array( 'type' => 'string', @@ -104,8 +135,16 @@ if ( ! class_exists( 'um\core\Blocks' ) ) { } } - - public function um_member_directories_render( $atts ) { + /** + * Renders member directory block. + * + * @param array $atts Block attributes. + * + * @return string + * + * @uses apply_shortcodes() + */ + public function member_directories_render( $atts ) { $shortcode = '[ultimatemember'; if ( isset( $atts['member_id'] ) && '' !== $atts['member_id'] ) { @@ -117,8 +156,16 @@ if ( ! class_exists( 'um\core\Blocks' ) ) { return apply_shortcodes( $shortcode ); } - - public function um_forms_render( $atts ) { + /** + * Renders UM Form block. + * + * @param array $atts Block attributes. + * + * @return string + * + * @uses apply_shortcodes() + */ + public function forms_render( $atts ) { if ( isset( $atts['form_id'] ) && '' !== $atts['form_id'] ) { $mode = get_post_meta( $atts['form_id'], '_um_mode', true ); if ( 'profile' === $mode && ( um_is_core_page( 'account' ) || um_is_core_page( 'user' ) ) ) { @@ -136,15 +183,29 @@ if ( ! class_exists( 'um\core\Blocks' ) ) { return apply_shortcodes( $shortcode ); } - - public function um_password_reset_render() { + /** + * Renders UM Reset Password form block. + * + * @return string + * + * @uses apply_shortcodes() + */ + public function password_reset_render() { $shortcode = '[ultimatemember_password]'; return apply_shortcodes( $shortcode ); } - - public function um_account_render( $atts ) { + /** + * Renders UM Account block. + * + * @param array $atts Block attributes. + * + * @return string + * + * @uses apply_shortcodes() + */ + public function account_render( $atts ) { if ( um_is_core_page( 'account' ) || um_is_core_page( 'user' ) ) { return ''; } diff --git a/includes/core/class-builtin.php b/includes/core/class-builtin.php index ea0b80fd..805b258a 100644 --- a/includes/core/class-builtin.php +++ b/includes/core/class-builtin.php @@ -1,12 +1,12 @@ array( '_title' => array( 'mode' => 'required', - 'error' => __('You must provide a title','ultimate-member') + 'error' => __( 'You must provide a title', 'ultimate-member' ), ), '_metakey' => array( 'mode' => 'unique', ), - ) + ), + ), + 'spotify' => array( + 'name' => __( 'Spotify URL', 'ultimate-member' ), + 'col1' => array('_title','_metakey','_help','_visibility'), + 'col2' => array('_label','_placeholder','_public','_roles','_validate','_custom_validate'), + 'col3' => array('_required','_editable','_icon'), + 'validate' => array( + '_title' => array( + 'mode' => 'required', + 'error' => __( 'You must provide a title', 'ultimate-member' ), + ), + '_metakey' => array( + 'mode' => 'unique', + ), + ), ), /*'group' => array( @@ -1497,6 +1512,7 @@ if ( ! class_exists( 'um\core\Builtin' ) ) { $array['unique_username_or_email'] = __('Unique Username/E-mail','ultimate-member'); $array['url'] = __('Website URL','ultimate-member'); $array['youtube_url'] = __('YouTube Profile','ultimate-member'); + $array['spotify_url'] = __('Spotify URL','ultimate-member'); $array['telegram_url'] = __('Telegram URL','ultimate-member'); $array['discord'] = __('Discord ID','ultimate-member'); $array['tiktok_url'] = __('TikTok URL','ultimate-member'); diff --git a/includes/core/class-external-integrations.php b/includes/core/class-external-integrations.php index 2d33de79..aa100f34 100644 --- a/includes/core/class-external-integrations.php +++ b/includes/core/class-external-integrations.php @@ -31,7 +31,8 @@ if ( ! class_exists( 'um\core\External_Integrations' ) ) { add_action( 'um_access_fix_external_post_content', array( &$this, 'forumwp_fix' ), 11 ); add_action( 'um_access_fix_external_post_content', array( &$this, 'woocommerce_fix' ), 12 ); - add_filter( 'um_localize_permalink_filter', array( &$this, 'um_localize_permalink_filter' ), 10, 2 ); + add_filter( 'um_external_profile_url', array( &$this, 'um_localize_profile_url' ), 10, 2 ); + add_filter( 'um_get_current_page_url', array( &$this, 'um_localize_profile_nav_url' ), 10, 2 ); add_filter( 'icl_ls_languages', array( &$this, 'um_core_page_wpml_permalink' ), 10, 1 ); // Integration for the "Transposh Translation Filter" plugin @@ -50,7 +51,6 @@ if ( ! class_exists( 'um\core\External_Integrations' ) ) { * UM filter - Restore original arguments on translated page * * @description Restore original arguments on load shortcode if they are missed in the WPML translation - * @hook um_pre_args_setup * * @global \SitePress $sitepress * @param array $args @@ -81,7 +81,6 @@ if ( ! class_exists( 'um\core\External_Integrations' ) ) { * Integration for the "Transposh Translation Filter" plugin * * @description Fix issue "404 Not Found" on profile page - * @hook template_redirect * @see http://transposh.org/ * * @global transposh_plugin $my_transposh_plugin @@ -175,76 +174,110 @@ if ( ! class_exists( 'um\core\External_Integrations' ) ) { return $single_template; } - /** - * @param $profile_url - * @param $page_id + * Change user profile URL for WPML compatibility. * - * @return bool|false|string + * @see \um\core\Permalinks::profile_permalink() + * + * @param bool|string $profile_url Profile URL. + * @param string $slug Profile slug. + * + * @return string */ - function um_localize_permalink_filter( $profile_url, $page_id ) { - - if ( ! $this->is_wpml_active() ) + public function um_localize_profile_url( $profile_url, $slug ) { + if ( ! $this->is_wpml_active() ) { return $profile_url; + } - /*if ( function_exists( 'icl_get_current_language' ) && icl_get_current_language() != icl_get_default_language() ) { - if ( get_the_ID() > 0 && get_post_meta( get_the_ID(), '_um_wpml_user', true ) == 1 ) { - $profile_url = get_permalink( get_the_ID() ); - } - }*/ + $page_id = UM()->config()->permalinks['user']; + $lang_post_id = apply_filters( 'wpml_object_id', $page_id, 'page', true ); - // WPML compatibility - if ( function_exists( 'icl_object_id' ) ) { - $language_code = ICL_LANGUAGE_CODE; - $lang_post_id = icl_object_id( $page_id , 'page', true, $language_code ); + if ( $lang_post_id && $lang_post_id !== $page_id ) { + $user_page_url = get_permalink( $lang_post_id ); - if ( $lang_post_id != 0 ) { - $profile_url = get_permalink( $lang_post_id ); + if ( UM()->is_permalinks ) { + if ( false === strpos( $user_page_url, '?' ) ) { + $profile_url = trailingslashit( $user_page_url ) . trailingslashit( $slug ); + } else { + $profile_url = str_replace( '?', trailingslashit( $slug ) . '?', $user_page_url ); + } } else { - // No page found, it's most likely the homepage - global $sitepress; - $profile_url = $sitepress->language_url( $language_code ); + $profile_url = add_query_arg( 'um_user', strtolower( $slug ), $user_page_url ); } } return $profile_url; } - /** - * @param $array + * Change current URL for WPML compatibility. * - * @return mixed + * Note: It's used for User Profile nav links fix + * + * @see \um\core\Permalinks::get_current_url() + * + * @param string $page_url Current URL. + * @param bool $no_query_params Ignore $_GET attributes in URL. "true" == ignore. + * + * @return string */ - function um_core_page_wpml_permalink( $array ) { - - if ( ! $this->is_wpml_active() ) - return $array; - - global $sitepress; - if( ! um_is_core_page("user") ) return $array; - if( ! defined("ICL_LANGUAGE_CODE") ) return $array; - if( ! function_exists('icl_object_id') ) return $array; - - // Permalink base - $permalink_base = UM()->options()->get( 'permalink_base' ); - - // Get user slug - $profile_slug = strtolower( get_user_meta( um_profile_id(), "um_user_profile_url_slug_{$permalink_base}", true ) ); - $current_language = ICL_LANGUAGE_CODE; - foreach ( $array as $lang_code => $arr ) { - $sitepress->switch_lang( $lang_code ); - $user_page = um_get_core_page( "user" ); - - $array[ $lang_code ]['url'] = "{$user_page}{$profile_slug}/"; + public function um_localize_profile_nav_url( $page_url, $no_query_params ) { + if ( ! $this->is_wpml_active() ) { + return $page_url; } - $sitepress->switch_lang( $current_language ); + if ( false === $no_query_params ) { + return $page_url; + } + + if ( ! empty( $_GET['lang'] ) ) { + $page_url = add_query_arg( 'lang', sanitize_key( $_GET['lang'] ), $page_url ); + } + + return $page_url; + } + + /** + * Filters the displayed languages of the WPML language switcher. + * + * @see https://wpml.org/wpml-hook/icl_ls_languages/ + * + * @param array $array Collection of active languages to display in the language switcher. + * + * @return array + */ + public function um_core_page_wpml_permalink( $array ) { + if ( $this->is_wpml_active() && um_is_core_page( 'user' ) && is_array( $array ) ) { + global $sitepress; + + $current_language = wpml_get_current_language(); + $slug = strtolower( UM()->user()->get_profile_slug( um_profile_id() ) ); + + foreach ( $array as $lang_code => $arr ) { + $sitepress->switch_lang( $lang_code ); + + $lang_post_id = apply_filters( 'wpml_object_id', UM()->config()->permalinks['user'], 'page', true ); + $user_page_url = get_permalink( $lang_post_id ); + + if ( UM()->is_permalinks ) { + if ( false === strpos( $user_page_url, '?' ) ) { + $profile_url = trailingslashit( $user_page_url ) . trailingslashit( $slug ); + } else { + $profile_url = str_replace( '?', trailingslashit( $slug ) . '?', $user_page_url ); + } + } else { + $profile_url = add_query_arg( 'um_user', strtolower( $slug ), $user_page_url ); + } + + $array[ $lang_code ]['url'] = $profile_url; + } + + $sitepress->switch_lang( $current_language ); + } return $array; } - /** * Check if WPML is active * diff --git a/includes/core/class-fields.php b/includes/core/class-fields.php index 3b197db9..34a3677a 100644 --- a/includes/core/class-fields.php +++ b/includes/core/class-fields.php @@ -1,13 +1,12 @@ get_atts( $key, $classes, $conditional, $data ) . '>'; @@ -2814,7 +2817,7 @@ if ( ! class_exists( 'um\core\Fields' ) ) { $output .= '
'; - if ( isset( $data['html'] ) && $data['html'] != 0 && $key != "description" ) { + if ( isset( $data['html'] ) && $data['html'] != 0 && 'description' !== $key ) { $textarea_settings = array( 'media_buttons' => false, @@ -2863,7 +2866,10 @@ if ( ! class_exists( 'um\core\Fields' ) ) { $output .= ob_get_clean(); $output .= '
' . $placeholder . ''; } else { - $textarea_field_value = ! empty( $data['html'] ) ? $field_value : strip_tags( $field_value ); + $textarea_field_value = ''; + if ( ! empty( $field_value ) ) { + $textarea_field_value = ! empty( $data['html'] ) ? $field_value : wp_strip_all_tags( $field_value ); + } $output .= ''; } @@ -3881,9 +3887,10 @@ if ( ! class_exists( 'um\core\Fields' ) ) { $um_field_checkbox_item_title = $v; - $v = $this->filter_field_non_utf8_value( $v ); + $v = $this->filter_field_non_utf8_value( $v ); + $value_attr = ( ! empty( $v ) && is_string( $v ) ) ? wp_strip_all_tags( $v ) : $v; - $output .= 'is_selected( $key, $v, $data ) ) { $output .= 'checked'; @@ -3892,10 +3899,9 @@ if ( ! class_exists( 'um\core\Fields' ) ) { $output .= ' />'; if ( ! empty( $disabled ) && $this->is_selected( $key, $v, $data ) ) { - $output .= $this->disabled_hidden_field( $key . '[]', strip_tags( $v ) ); + $output .= $this->disabled_hidden_field( $key . '[]', $value_attr ); } - $output .= ''; /** * UM hook diff --git a/includes/core/class-fonticons.php b/includes/core/class-fonticons.php index 6d964a23..9d7abd74 100644 --- a/includes/core/class-fonticons.php +++ b/includes/core/class-fonticons.php @@ -1,42 +1,47 @@ $file ) { - $css = file_get_contents( $file ); - if ( $c == 'fa' ) { - preg_match_all('/\.(um-faicon-.*?):before/', $css, $matches); + if ( 'fa' === $c ) { + preg_match_all( '/\.(um-faicon-.*?):before/', $css, $matches ); } else { - preg_match_all('/\.(um-icon-.*?):before/', $css, $matches); + preg_match_all( '/\.(um-icon-.*?):before/', $css, $matches ); } foreach ( $matches[1] as $match ) { - $icon = str_replace( ':before', '', $match ); + $icon = str_replace( ':before', '', $match ); $array[] = $icon; } $array = array_unique( $array ); @@ -45,9 +50,7 @@ if ( ! class_exists( 'um\core\FontIcons' ) ) { update_option( 'um_cache_fonticons', $array ); } - $this->all = get_option( 'um_cache_fonticons' ); - + $this->all = $cached_option; } - } -} \ No newline at end of file +} diff --git a/includes/core/class-form.php b/includes/core/class-form.php index 1f958d90..bbe6d10f 100644 --- a/includes/core/class-form.php +++ b/includes/core/class-form.php @@ -1,12 +1,12 @@ get_blog_id(); diff --git a/includes/core/class-member-directory-meta.php b/includes/core/class-member-directory-meta.php index 268934ed..75378531 100644 --- a/includes/core/class-member-directory-meta.php +++ b/includes/core/class-member-directory-meta.php @@ -1,27 +1,36 @@ get_var( $wpdb->prepare( - "SELECT umeta_id - FROM {$wpdb->prefix}um_metadata - WHERE user_id = %d AND - um_key = %s + "SELECT umeta_id + FROM {$wpdb->prefix}um_metadata + WHERE user_id = %d AND + um_key = %s LIMIT 1", $object_id, $meta_key @@ -501,7 +510,6 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) { } } - $profile_photo_where = ''; if ( $directory_data['has_profile_photo'] == 1 ) { $profile_photo_where = " AND umm_general.um_value LIKE '%s:13:\"profile_photo\";b:1;%'"; @@ -517,7 +525,7 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) { $this->joins[] = "LEFT JOIN {$wpdb->prefix}um_metadata umm_general ON umm_general.user_id = u.ID"; $this->general_meta_joined = true; } - $this->where_clauses[] = "( umm_general.um_key = 'um_member_directory_data' AND + $this->where_clauses[] = "( umm_general.um_key = 'um_member_directory_data' AND umm_general.um_value LIKE '%s:14:\"account_status\";s:8:\"approved\";%' AND umm_general.um_value LIKE '%s:15:\"hide_in_members\";b:0;%'{$profile_photo_where}{$cover_photo_where} )"; } else { if ( ! empty( $cover_photo_where ) || ! empty( $profile_photo_where ) ) { @@ -578,7 +586,6 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) { } } - if ( ! empty( $_POST['search'] ) ) { $search_line = trim( stripslashes( sanitize_text_field( $_POST['search'] ) ) ); @@ -600,7 +607,6 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) { $this->is_search = true; } - //filters $filter_query = array(); if ( ! empty( $directory_data['search_fields'] ) ) { @@ -638,7 +644,6 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) { } } - //unable default filter in case if we select other filters in frontend filters //if ( empty( $this->custom_filters_in_query ) ) { $default_filters = array(); @@ -676,7 +681,7 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) { if ( ! empty( UM()->builtin()->saved_fields ) ) { foreach ( UM()->builtin()->saved_fields as $key => $data ) { - if ( $key == '_um_last_login' ) { + if ( '_um_last_login' === $key ) { continue; } @@ -694,12 +699,29 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) { // handle sorting options // sort members by if ( $sortby == $directory_data['sortby_custom'] || in_array( $sortby, $custom_sort ) ) { + $custom_sort_order = ! empty( $directory_data['sortby_custom_order'] ) ? $directory_data['sortby_custom_order'] : 'ASC'; $this->joins[] = "LEFT JOIN {$wpdb->prefix}um_metadata umm_sort ON ( umm_sort.user_id = u.ID AND umm_sort.um_key = '{$sortby}' )"; - $custom_sort_type = apply_filters( 'um_member_directory_custom_sorting_type', 'CHAR', $sortby, $directory_data ); + $meta_query = new \WP_Meta_Query(); + $custom_sort_type = ! empty( $directory_data['sortby_custom_type'] ) ? $meta_query->get_cast_for_type( $directory_data['sortby_custom_type'] ) : 'CHAR'; + if ( ! empty( $directory_data['sorting_fields'] ) ) { + // phpcs:ignore WordPress.Security.NonceVerification -- already verified here + $sorting = sanitize_text_field( $_POST['sorting'] ); + $sorting_fields = maybe_unserialize( $directory_data['sorting_fields'] ); - $this->sql_order = " ORDER BY CAST( umm_sort.um_value AS {$custom_sort_type} ) {$order} "; + foreach ( $sorting_fields as $field ) { + if ( isset( $field[ $sorting ] ) ) { + $custom_sort_type = ! empty( $field['type'] ) ? $meta_query->get_cast_for_type( $field['type'] ) : 'CHAR'; + $custom_sort_order = $field['order']; + } + } + } + + /** This filter is documented in includes/core/class-member-directory.php */ + $custom_sort_type = apply_filters( 'um_member_directory_custom_sorting_type', $custom_sort_type, $sortby, $directory_data ); + + $this->sql_order = " ORDER BY CAST( umm_sort.um_value AS {$custom_sort_type} ) {$custom_sort_order} "; } elseif ( count( $numeric_sorting_keys ) && in_array( $sortby, $numeric_sorting_keys ) ) { @@ -802,7 +824,6 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) { $this->sql_order = apply_filters( 'um_modify_sortby_parameter_meta', $this->sql_order, $sortby ); - $profiles_per_page = $directory_data['profiles_per_page']; if ( UM()->mobile()->isMobile() && isset( $directory_data['profiles_per_page_mobile'] ) ) { $profiles_per_page = $directory_data['profiles_per_page_mobile']; @@ -856,25 +877,22 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) { $total_users = (int) $wpdb->get_var( 'SELECT FOUND_ROWS()' ); /** - * UM hook + * Filters the member directory query result when um_usermeta table is used. * - * @type filter - * @title um_prepare_user_results_array_meta - * @description Extend member directory query result - * @input_vars - * [{"var":"$result","type":"array","desc":"Members Query Result"}] - * @change_log - * ["Since: 2.0"] - * @usage - * - * @example - * Remove some users where ID equals 10 and 12 from query. + * function my_custom_um_prepare_user_results_array_meta( $user_ids, $query_args ) { + * $user_ids = array_diff( $user_ids, array( 10, 12 ) ); * return $user_ids; * } - * ?> + * add_filter( 'um_prepare_user_results_array_meta', 'my_custom_um_prepare_user_results_array', 10, 2 ); */ $user_ids = apply_filters( 'um_prepare_user_results_array_meta', $user_ids, $query ); diff --git a/includes/core/class-member-directory.php b/includes/core/class-member-directory.php index b04cbca9..ec104cd9 100644 --- a/includes/core/class-member-directory.php +++ b/includes/core/class-member-directory.php @@ -1,20 +1,18 @@ ! empty( $metalabel ) ? $metalabel : $metakey, + $metakey => $metakey, + 'label' => ! empty( $metalabel ) ? $metalabel : $metakey, + 'type' => ! empty( $data_type ) ? $data_type : '', + 'order' => ! empty( $order ) ? $order : '', ); } } @@ -275,6 +285,10 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) { $value = sanitize_text_field( $value ); } elseif ( '_um_sortby_custom_label' === $key ) { $value = wp_strip_all_tags( $value ); + } elseif ( '_um_sortby_custom_type' === $key ) { + $value = sanitize_text_field( $value ); + } elseif ( '_um_sortby_custom_order' === $key ) { + $value = sanitize_text_field( $value ); } } @@ -308,6 +322,20 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) { $this->sorting_supported_fields = apply_filters( 'um_members_directory_custom_field_types_supported_sorting', array( 'number' ) ); + $this->sort_data_types = array( + 'CHAR' => __( 'CHAR', 'ultimate-member' ), + 'NUMERIC' => __( 'NUMERIC', 'ultimate-member' ), + 'BINARY' => __( 'BINARY', 'ultimate-member' ), + 'DATE' => __( 'DATE', 'ultimate-member' ), + 'DATETIME' => __( 'DATETIME', 'ultimate-member' ), + 'DECIMAL' => __( 'DECIMAL', 'ultimate-member' ), + 'SIGNED' => __( 'SIGNED', 'ultimate-member' ), + 'TIME' => __( 'TIME', 'ultimate-member' ), + 'UNSIGNED' => __( 'UNSIGNED', 'ultimate-member' ), + ); + + $this->sort_data_types = apply_filters( 'um_members_directory_sort_data_types', $this->sort_data_types ); + if ( ! empty( UM()->builtin()->saved_fields ) ) { foreach ( UM()->builtin()->saved_fields as $key => $data ) { if ( $key == '_um_last_login' ) { @@ -1422,23 +1450,64 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) { unset( $this->query_args['order'] ); } elseif ( ( ! empty( $directory_data['sortby_custom'] ) && $sortby == $directory_data['sortby_custom'] ) || in_array( $sortby, $custom_sort ) ) { + $custom_sort_order = ! empty( $directory_data['sortby_custom_order'] ) ? $directory_data['sortby_custom_order'] : 'ASC'; - $custom_sort_type = apply_filters( 'um_member_directory_custom_sorting_type', 'CHAR', $sortby, $directory_data ); + $meta_query = new \WP_Meta_Query(); + $custom_sort_type = ! empty( $directory_data['sortby_custom_type'] ) ? $meta_query->get_cast_for_type( $directory_data['sortby_custom_type'] ) : 'CHAR'; + if ( ! empty( $directory_data['sorting_fields'] ) ) { + // phpcs:ignore WordPress.Security.NonceVerification -- already verified here + $sorting = sanitize_text_field( $_POST['sorting'] ); + $sorting_fields = maybe_serialize( $directory_data['sorting_fields'] ); + + foreach ( $sorting_fields as $field ) { + if ( isset( $field[ $sorting ] ) ) { + $custom_sort_type = ! empty( $field['type'] ) ? $meta_query->get_cast_for_type( $field['type'] ) : 'CHAR'; + $custom_sort_order = $field['order']; + } + } + } + /** + * Filters the sorting MySQL type in member directory custom sorting query. + * + * Note: Possible MySQL types are BINARY|CHAR|DATE|DATETIME|SIGNED|UNSIGNED|TIME|DECIMAL + * + * @since 2.1.3 + * @hook um_member_directory_custom_sorting_type + * + * @param {string} $custom_sort_type MySQL type to cast meta_value. 'CHAR' is default. + * @param {string} $sortby meta_key used for sorting. + * @param {array} $directory_data Member directory data. + * + * @return {string} MySQL type to cast meta_value. + * @example Change type to DATE by the directory ID and mete_key. + * function my_um_member_directory_custom_sorting_type( $custom_sort_type, $sortby, $directory_data ) { + * if ( '{selected member directory ID}' == $directory_data['form_id'] && '{custom_date_key}' === $sortby ) { + * $custom_sort_type = 'DATE'; + * } + * + * return $custom_sort_type; + * } + * add_filter( 'um_member_directory_custom_sorting_type', 'my_um_member_directory_custom_sorting_type', 10, 3 ); + */ + $custom_sort_type = apply_filters( 'um_member_directory_custom_sorting_type', $custom_sort_type, $sortby, $directory_data ); $this->query_args['meta_query'][] = array( - 'relation' => 'OR', + 'relation' => 'OR', $sortby . '_cs' => array( - 'key' => $sortby, - 'compare' => 'EXISTS', - 'type' => $custom_sort_type, + 'key' => $sortby, + 'compare' => 'EXISTS', + 'type' => $custom_sort_type, ), array( - 'key' => $sortby, - 'compare' => 'NOT EXISTS', - ) + 'key' => $sortby, + 'compare' => 'NOT EXISTS', + ), ); - $this->query_args['orderby'] = array( $sortby . '_cs' => 'ASC', 'user_login' => 'ASC' ); + $this->query_args['orderby'] = array( + $sortby . '_cs' => $custom_sort_order, + 'user_login' => 'ASC', + ); } else { @@ -2648,29 +2717,25 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) { $user_ids = ! empty( $user_query->results ) ? array_unique( $user_query->results ) : array(); /** - * UM hook + * Filters the member directory query result. * - * @type filter - * @title um_prepare_user_results_array - * @description Extend member directory query result - * @input_vars - * [{"var":"$result","type":"array","desc":"Members Query Result"}] - * @change_log - * ["Since: 2.0"] - * @usage - * - * @example - * Remove some users where ID equals 10 and 12 from query. + * function my_custom_um_prepare_user_results_array( $user_ids, $query_args ) { + * $user_ids = array_diff( $user_ids, array( 10, 12 ) ); * return $user_ids; * } - * ?> + * add_filter( 'um_prepare_user_results_array', 'my_custom_um_prepare_user_results_array', 10, 2 ); */ $user_ids = apply_filters( 'um_prepare_user_results_array', $user_ids, $this->query_args ); - $sizes = UM()->options()->get( 'cover_thumb_sizes' ); $this->cover_size = UM()->mobile()->isTablet() ? $sizes[1] : end( $sizes ); @@ -2762,5 +2827,27 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) { wp_send_json_success( array( 'field_html' => $html ) ); } + + /** + * Get member directory id by page id. + * + * @param int $page_id Page ID. + * + * @return array Member directories ID. + */ + public function get_member_directory_id( $page_id ) { + $members_page = get_post( $page_id ); + if ( ! empty( $members_page ) && ! is_wp_error( $members_page ) ) { + if ( ! empty( $members_page->post_content ) ) { + preg_match_all( '/\[ultimatemember[^\]]*?form_id\=[\'"]*?(\d+)[\'"]*?/i', $members_page->post_content, $matches ); + if ( ! empty( $matches[1] ) && is_array( $matches[1] ) ) { + $member_directory_ids = array_map( 'absint', $matches[1] ); + return $member_directory_ids; + } + } + } + + return array(); + } } } diff --git a/includes/core/class-permalinks.php b/includes/core/class-permalinks.php index 96a58880..59f9e6cd 100644 --- a/includes/core/class-permalinks.php +++ b/includes/core/class-permalinks.php @@ -1,35 +1,32 @@ current_url = $this->get_current_url(); } - - /** - * SEO canonical href bugfix - * - * @deprecated since version 2.1.7 - * - * @see function um_profile_remove_wpseo() - */ - function um_rel_canonical_() { - global $wp_the_query; - - if ( ! is_singular() ) - return; - - /** - * UM hook - * - * @type filter - * @title um_allow_canonical__filter - * @description Allow canonical - * @input_vars - * [{"var":"$allow_canonical","type":"bool","desc":"Allow?"}] - * @change_log - * ["Since: 2.0"] - * @usage - * - * @example - * - */ - $enable_canonical = apply_filters( "um_allow_canonical__filter", true ); - - if( ! $enable_canonical ) - return; - - if ( !$id = $wp_the_query->get_queried_object_id() ) - return; - - if ( UM()->config()->permalinks['user'] == $id ) { - $link = esc_url( $this->get_current_url() ); - echo "\n"; - return; - } - - $link = get_permalink( $id ); - if ( $page = get_query_var( 'cpage' ) ){ - $link = get_comments_pagenum_link( $page ); - echo "\n"; - } - - } - - /** * Get query as array * * @return array */ - function get_query_array() { + public function get_query_array() { $parts = parse_url( $this->get_current_url() ); if ( isset( $parts['query'] ) ) { parse_str( $parts['query'], $query ); @@ -118,66 +56,61 @@ if ( ! class_exists( 'um\core\Permalinks' ) ) { return array(); } - /** * Get current URL anywhere * * @param bool $no_query_params * - * @return mixed|void + * @return string */ - function get_current_url( $no_query_params = false ) { + public function get_current_url( $no_query_params = false ) { //use WP native function for fill $_SERVER variables by correct values wp_fix_server_vars(); //check if WP-CLI there isn't set HTTP_HOST, use localhost instead if ( defined( 'WP_CLI' ) && WP_CLI ) { $host = isset( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : 'localhost'; - } else{ - if ( isset( $_SERVER['HTTP_HOST'] ) ) { - $host = $_SERVER['HTTP_HOST']; - }else{ - $host = 'localhost'; - } - } + } else { + if ( isset( $_SERVER['HTTP_HOST'] ) ) { + $host = $_SERVER['HTTP_HOST']; + } else { + $host = 'localhost'; + } + } $page_url = ( is_ssl() ? 'https://' : 'http://' ) . $host . $_SERVER['REQUEST_URI']; - if ( $no_query_params == true ) { + if ( false !== $no_query_params ) { $page_url = strtok( $page_url, '?' ); } /** - * UM hook + * Filters current page URL. * - * @type filter - * @title um_get_current_page_url - * @description Change current page URL - * @input_vars - * [{"var":"$page_url","type":"string","desc":"Page URL"}] - * @change_log - * ["Since: 2.0"] - * @usage - * - * @example - * Add your custom $_GET attribute to all links. + * function my_um_get_current_page_url( $page_url, $no_query_params ) { + * $page_url = add_query_arg( '{attr_value}', '{attr_key}', $page_url ); // replace to your custom value and key. * return $page_url; * } - * ?> + * add_filter( 'um_get_current_page_url', 'my_um_get_current_page_url', 10, 2 ); */ - return apply_filters( 'um_get_current_page_url', $page_url ); + return apply_filters( 'um_get_current_page_url', $page_url, $no_query_params ); } - /** * Activates an account via email */ public function activate_account_via_email_link() { if ( isset( $_REQUEST['act'] ) && 'activate_via_email' === sanitize_key( $_REQUEST['act'] ) && isset( $_REQUEST['hash'] ) && is_string( $_REQUEST['hash'] ) && strlen( $_REQUEST['hash'] ) == 40 && - isset( $_REQUEST['user_id'] ) && is_numeric( $_REQUEST['user_id'] ) ) { // valid token + isset( $_REQUEST['user_id'] ) && is_numeric( $_REQUEST['user_id'] ) ) { // valid token $user_id = absint( $_REQUEST['user_id'] ); delete_option( "um_cache_userdata_{$user_id}" ); @@ -238,18 +171,15 @@ if ( ! class_exists( 'um\core\Permalinks' ) ) { $redirect = apply_filters( 'um_after_email_confirmation_redirect', $redirect, $user_id, $login ); exit( wp_redirect( $redirect ) ); - } - } - /** * Makes an activate link for any user * * @return bool|string */ - function activate_url() { + public function activate_url() { if ( ! um_user( 'account_secret_hash' ) ) { return false; } @@ -283,17 +213,15 @@ if ( ! class_exists( 'um\core\Permalinks' ) ) { return $url; } - /** * Checks for UM query strings */ - function check_for_querystrings() { + public function check_for_querystrings() { if ( isset( $_REQUEST['message'] ) ) { UM()->shortcodes()->message_mode = true; } } - /** * Add a query param to url * @@ -302,12 +230,11 @@ if ( ! class_exists( 'um\core\Permalinks' ) ) { * * @return string */ - function add_query( $key, $value ) { + public function add_query( $key, $value ) { $this->current_url = add_query_arg( $key, $value, $this->get_current_url() ); return $this->current_url; } - /** * Remove a query param from url * @@ -316,26 +243,25 @@ if ( ! class_exists( 'um\core\Permalinks' ) ) { * * @return string */ - function remove_query( $key, $value ) { + public function remove_query( $key, $value ) { $this->current_url = remove_query_arg( $key, $this->current_url ); return $this->current_url; } - /** * @param $slug * * @return int|null|string */ - function slug_exists_user_id( $slug ) { + public function slug_exists_user_id( $slug ) { global $wpdb; $permalink_base = UM()->options()->get( 'permalink_base' ); $user_id = $wpdb->get_var( - "SELECT user_id - FROM {$wpdb->usermeta} - WHERE meta_key = 'um_user_profile_url_slug_{$permalink_base}' AND + "SELECT user_id + FROM {$wpdb->usermeta} + WHERE meta_key = 'um_user_profile_url_slug_{$permalink_base}' AND meta_value = '{$slug}' ORDER BY umeta_id ASC LIMIT 1" @@ -348,57 +274,93 @@ if ( ! class_exists( 'um\core\Permalinks' ) ) { return false; } - /** * Get Profile Permalink * * @param string $slug - * @return string $profile_url + * + * @return string */ - function profile_permalink( $slug ) { + public function profile_permalink( $slug ) { + /** + * Filters user profile URL externally with own logic. + * + * @since 2.6.3 + * @hook um_external_profile_url + * + * @param {bool|string} $profile_url Profile URL. + * @param {string} $slug User profile slug. + * + * @return {string} Profile URL. + * + * @example Change profile URL to your custom link and ignore native profile permalink handlers. + * function my_um_external_profile_url( $profile_url, $slug ) { + * $profile_url = '{some your custom URL}'; // replace to your custom link. + * return $profile_url; + * } + * add_filter( 'um_external_profile_url', 'my_um_external_profile_url', 10, 2 ); + */ + $external_profile_url = apply_filters( 'um_external_profile_url', false, $slug ); + if ( false !== $external_profile_url ) { + return ! empty( $external_profile_url ) ? $external_profile_url : ''; + } - $page_id = UM()->config()->permalinks['user']; + $page_id = UM()->config()->permalinks['user']; $profile_url = get_permalink( $page_id ); /** - * UM hook + * Filters the base URL of the UM profile page. * - * @type filter - * @title um_localize_permalink_filter - * @description Change user profile URL - * @input_vars - * [{"var":"$profile_url","type":"string","desc":"Profile URL"}, - * {"var":"$page_id","type":"int","desc":"Profile Page ID"}] - * @change_log - * ["Since: 2.0"] - * @usage - * - * @example - * 'post_link' instead. + * @hook um_localize_permalink_filter + * @todo fully remove since 2.7.0 + * + * @param {string} $profile_url Profile URL. + * @param {int} $page_id Profile Page ID. + * + * @return {string} Profile URL. + * + * @example Change profile base URL to your custom link. * function my_localize_permalink( $profile_url, $page_id ) { - * // your code here + * $profile_url = '{some your custom URL}'; // replace to your custom link. * return $profile_url; * } - * ?> + * add_filter( 'um_localize_permalink_filter', 'my_localize_permalink', 10, 2 ); */ $profile_url = apply_filters( 'um_localize_permalink_filter', $profile_url, $page_id ); - if ( get_option( 'permalink_structure' ) ) { - - $profile_url = trailingslashit( untrailingslashit( $profile_url ) ); - $profile_url = $profile_url . strtolower( $slug ). '/'; - + if ( UM()->is_permalinks ) { + $profile_url = trailingslashit( untrailingslashit( $profile_url ) ); + $profile_url .= trailingslashit( strtolower( $slug ) ); } else { - - $profile_url = add_query_arg( 'um_user', strtolower( $slug ), $profile_url ); - + $profile_url = add_query_arg( 'um_user', strtolower( $slug ), $profile_url ); } + /** + * Filters user profile URL. + * + * @since 2.6.3 + * @hook um_profile_permalink + * + * @param {string} $profile_url Profile URL. + * @param {int} $page_id Profile Page ID. + * @param {string} $slug User profile slug. + * + * @return {string} Profile URL. + * + * @example Change profile URL to your custom link. + * function my_um_profile_permalink( $profile_url, $page_id, $slug ) { + * $profile_url = '{some your custom URL}'; // replace to your custom link. + * return $profile_url; + * } + * add_filter( 'um_profile_permalink', 'my_um_profile_permalink', 10, 3 ); + */ + $profile_url = apply_filters( 'um_profile_permalink', $profile_url, $page_id, $slug ); + return ! empty( $profile_url ) ? $profile_url : ''; } - /** * Generate profile slug * @@ -407,41 +369,38 @@ if ( ! class_exists( 'um\core\Permalinks' ) ) { * @param string $last_name * @return string */ - function profile_slug( $full_name, $first_name, $last_name ){ - + public function profile_slug( $full_name, $first_name, $last_name ) { $permalink_base = UM()->options()->get( 'permalink_base' ); $user_in_url = ''; - $full_name = str_replace("'", "", $full_name ); - $full_name = str_replace("&", "", $full_name ); - $full_name = str_replace("/", "", $full_name ); + $full_name = str_replace( "'", "", $full_name ); + $full_name = str_replace( "&", "", $full_name ); + $full_name = str_replace( "/", "", $full_name ); - switch( $permalink_base ) { + switch ( $permalink_base ) { case 'name': // dotted - $full_name_slug = $full_name; $difficulties = 0; - - if( strpos( $full_name, '.' ) > -1 ){ - $full_name = str_replace(".", "_", $full_name ); + if ( strpos( $full_name, '.' ) > -1 ) { + $full_name = str_replace( ".", "_", $full_name ); $difficulties++; } $full_name = strtolower( str_replace( " ", ".", $full_name ) ); - if( strpos( $full_name, '_.' ) > -1 ){ - $full_name = str_replace('_.', '_', $full_name ); + if ( strpos( $full_name, '_.' ) > -1 ) { + $full_name = str_replace( '_.', '_', $full_name ); $difficulties++; } - $full_name_slug = str_replace( '-' , '.', $full_name_slug ); - $full_name_slug = str_replace( ' ' , '.', $full_name_slug ); - $full_name_slug = str_replace( '..' , '.', $full_name_slug ); + $full_name_slug = str_replace( '-', '.', $full_name_slug ); + $full_name_slug = str_replace( ' ', '.', $full_name_slug ); + $full_name_slug = str_replace( '..', '.', $full_name_slug ); - if( strpos( $full_name, '.' ) > -1 ){ - $full_name = str_replace('.', ' ', $full_name ); + if ( strpos( $full_name, '.' ) > -1 ) { + $full_name = str_replace( '.', ' ', $full_name ); $difficulties++; } @@ -450,76 +409,73 @@ if ( ! class_exists( 'um\core\Permalinks' ) ) { break; case 'name_dash': // dashed - $difficulties = 0; $full_name_slug = strtolower( $full_name ); // if last name has dashed replace with underscore - if( strpos( $last_name, '-') > -1 && strpos( $full_name, '-' ) > -1 ){ + if ( strpos( $last_name, '-' ) > -1 && strpos( $full_name, '-' ) > -1 ) { $difficulties++; - $full_name = str_replace('-', '_', $full_name ); + $full_name = str_replace( '-', '_', $full_name ); } // if first name has dashed replace with underscore - if( strpos( $first_name, '-') > -1 && strpos( $full_name, '-' ) > -1 ){ + if ( strpos( $first_name, '-' ) > -1 && strpos( $full_name, '-' ) > -1 ) { $difficulties++; - $full_name = str_replace('-', '_', $full_name ); + $full_name = str_replace( '-', '_', $full_name ); } // if name has space, replace with dash - $full_name_slug = str_replace( ' ' , '-', $full_name_slug ); + $full_name_slug = str_replace( ' ', '-', $full_name_slug ); // if name has period - if( strpos( $last_name, '.') > -1 && strpos( $full_name, '.' ) > -1 ){ + if ( strpos( $last_name, '.' ) > -1 && strpos( $full_name, '.' ) > -1 ) { $difficulties++; } - $full_name_slug = str_replace( '.' , '-', $full_name_slug ); - $full_name_slug = str_replace( '--' , '-', $full_name_slug ); + $full_name_slug = str_replace( '.', '-', $full_name_slug ); + $full_name_slug = str_replace( '--', '-', $full_name_slug ); - $user_in_url = rawurlencode( $full_name_slug ); + $user_in_url = rawurlencode( $full_name_slug ); break; case 'name_plus': // plus - $difficulties = 0; $full_name_slug = strtolower( $full_name ); // if last name has dashed replace with underscore - if( strpos( $last_name, '+') > -1 && strpos( $full_name, '+' ) > -1 ){ + if ( strpos( $last_name, '+' ) > -1 && strpos( $full_name, '+' ) > -1 ) { $difficulties++; - $full_name = str_replace('-', '_', $full_name ); + $full_name = str_replace( '-', '_', $full_name ); } // if first name has dashed replace with underscore - if( strpos( $first_name, '+') > -1 && strpos( $full_name, '+' ) > -1 ){ + if ( strpos( $first_name, '+' ) > -1 && strpos( $full_name, '+' ) > -1 ) { $difficulties++; - $full_name = str_replace('-', '_', $full_name ); + $full_name = str_replace( '-', '_', $full_name ); } - if( strpos( $last_name, '-') > -1 || strpos( $first_name, '-') > -1 || strpos( $full_name, '-') > -1 ){ + if ( strpos( $last_name, '-' ) > -1 || strpos( $first_name, '-' ) > -1 || strpos( $full_name, '-' ) > -1 ) { $difficulties++; } // if name has space, replace with dash - $full_name_slug = str_replace( ' ' , '+', $full_name_slug ); - $full_name_slug = str_replace( '-' , '+', $full_name_slug ); + $full_name_slug = str_replace( ' ', '+', $full_name_slug ); + $full_name_slug = str_replace( '-', '+', $full_name_slug ); // if name has period - if( strpos( $last_name, '.') > -1 && strpos( $full_name, '.' ) > -1 ){ + if ( strpos( $last_name, '.' ) > -1 && strpos( $full_name, '.' ) > -1 ) { $difficulties++; } - $full_name_slug = str_replace( '.' , '+', $full_name_slug ); - $full_name_slug = str_replace( '++' , '+', $full_name_slug ); + $full_name_slug = str_replace( '.', '+', $full_name_slug ); + $full_name_slug = str_replace( '++', '+', $full_name_slug ); $user_in_url = $full_name_slug; break; } - return $user_in_url ; + return $user_in_url; } - /** * Get action url for admin use * @@ -528,12 +484,69 @@ if ( ! class_exists( 'um\core\Permalinks' ) ) { * * @return mixed|string|void */ - function admin_act_url( $action, $subaction ) { + public function admin_act_url( $action, $subaction ) { $url = $this->get_current_url(); - $url = add_query_arg( 'um_adm_action', $action, $url ); - $url = add_query_arg( 'sub', $subaction, $url ); - $url = add_query_arg( 'user_id', um_user('ID'), $url ); + $url = add_query_arg( 'um_adm_action', $action, $url ); + $url = add_query_arg( 'sub', $subaction, $url ); + $url = add_query_arg( 'user_id', um_user( 'ID' ), $url ); return $url; } + + /** + * SEO canonical href bugfix + * + * @deprecated since version 2.1.7 + * + * @todo remove since 2.7.0 + * @see function um_profile_remove_wpseo() + */ + public function um_rel_canonical_() { + _deprecated_function( __METHOD__, '2.1.7', 'um_profile_remove_wpseo()' ); + global $wp_the_query; + + if ( ! is_singular() ) + return; + + /** + * UM hook + * + * @type filter + * @title um_allow_canonical__filter + * @description Allow canonical + * @input_vars + * [{"var":"$allow_canonical","type":"bool","desc":"Allow?"}] + * @change_log + * ["Since: 2.0"] + * @usage + * + * @example + * + */ + $enable_canonical = apply_filters( "um_allow_canonical__filter", true ); + + if( ! $enable_canonical ) + return; + + if ( !$id = $wp_the_query->get_queried_object_id() ) + return; + + if ( UM()->config()->permalinks['user'] == $id ) { + $link = esc_url( $this->get_current_url() ); + echo "\n"; + return; + } + + $link = get_permalink( $id ); + if ( $page = get_query_var( 'cpage' ) ) { + $link = get_comments_pagenum_link( $page ); + echo "\n"; + } + } } } diff --git a/includes/core/class-roles-capabilities.php b/includes/core/class-roles-capabilities.php index 8155b576..9b60c143 100644 --- a/includes/core/class-roles-capabilities.php +++ b/includes/core/class-roles-capabilities.php @@ -91,40 +91,47 @@ if ( ! class_exists( 'um\core\Roles_Capabilities' ) ) { } } - /** * Loop through dynamic roles and add them to the $wp_roles array * * @param null|object $wp_roles * @return null */ - function um_roles_init( $wp_roles = null ) { + public function um_roles_init( $wp_roles = null ) { + $role_keys = get_option( 'um_roles', array() ); + $um_roles = array_map( array( &$this, 'key_to_role_id_mapping' ), $role_keys ); - //Add UM role data to WP Roles - foreach ( $wp_roles->roles as $roleID => $role_data ) { - $role_meta = get_option( "um_role_{$roleID}_meta" ); + // Add UM role data to WP Roles. + foreach ( $wp_roles->roles as $role_id => $role_data ) { + // Skip custom UM roles meta here, because it's added below. See: "Add custom UM roles". + if ( in_array( $role_id, $um_roles, true ) ) { + continue; + } + $role_meta = get_option( "um_role_{$role_id}_meta" ); if ( ! empty( $role_meta ) ) { - $wp_roles->roles[ $roleID ] = array_merge( $role_data, $role_meta ); + $wp_roles->roles[ $role_id ] = array_merge( $role_data, $role_meta ); } } - - //Add custom UM roles + // Add custom UM roles. $roles = array(); - - $role_keys = get_option( 'um_roles', array() ); foreach ( $role_keys as $role_key ) { $role_meta = get_option( "um_role_{$role_key}_meta" ); - if ( $role_meta ) { + if ( ! empty( $role_meta ) ) { $roles[ 'um_' . $role_key ] = $role_meta; } } + if ( empty( $roles ) ) { + return $wp_roles; + } + foreach ( $roles as $role_id => $details ) { - $capabilities = ! empty( $details['wp_capabilities'] ) ? array_keys( $details['wp_capabilities'] ) : array(); + $capabilities = ! empty( $details['wp_capabilities'] ) ? array_keys( $details['wp_capabilities'] ) : array(); $details['capabilities'] = array_fill_keys( array_values( $capabilities ), true ); unset( $details['wp_capabilities'] ); + $wp_roles->roles[ $role_id ] = $details; $wp_roles->role_objects[ $role_id ] = new \WP_Role( $role_id, $details['capabilities'] ); $wp_roles->role_names[ $role_id ] = $details['name']; @@ -134,6 +141,9 @@ if ( ! class_exists( 'um\core\Roles_Capabilities' ) ) { return $wp_roles; } + public function key_to_role_id_mapping( $role_key ) { + return 'um_' . $role_key; + } /** * Check if role is custom @@ -141,22 +151,17 @@ if ( ! class_exists( 'um\core\Roles_Capabilities' ) ) { * @param $role * @return bool */ - function is_role_custom( $role ) { + public function is_role_custom( $role ) { // User has roles so look for a UM Role one $role_keys = get_option( 'um_roles', array() ); - if ( empty( $role_keys ) ) { return false; } - $role_keys = array_map( function( $item ) { - return 'um_' . $item; - }, $role_keys ); - - return in_array( $role, $role_keys ); + $um_roles = array_map( array( &$this, 'key_to_role_id_mapping' ), $role_keys ); + return in_array( $role, $um_roles, true ); } - /** * Return a user's main role * diff --git a/includes/core/class-shortcodes.php b/includes/core/class-shortcodes.php index c87ac86d..e300d2e2 100644 --- a/includes/core/class-shortcodes.php +++ b/includes/core/class-shortcodes.php @@ -1260,7 +1260,7 @@ if ( ! class_exists( 'um\core\Shortcodes' ) ) { * * @return string */ - public function ultimatemember_searchform( $args = array(), $content = "" ) { + public function ultimatemember_searchform( $args = array(), $content = '' ) { if ( ! UM()->options()->get( 'members_page' ) ) { return ''; } @@ -1269,15 +1269,7 @@ if ( ! class_exists( 'um\core\Shortcodes' ) ) { $page_id = UM()->config()->permalinks['members']; if ( ! empty( $page_id ) ) { - $members_page = get_post( $page_id ); - if ( ! empty( $members_page ) && ! is_wp_error( $members_page ) ) { - if ( ! empty( $members_page->post_content ) ) { - preg_match_all( '/\[ultimatemember[^\]]*?form_id\=[\'"]*?(\d+)[\'"]*?/i', $members_page->post_content, $matches ); - if ( ! empty( $matches[1] ) && is_array( $matches[1] ) ) { - $member_directory_ids = array_map( 'absint', $matches[1] ); - } - } - } + $member_directory_ids = UM()->member_directory()->get_member_directory_id( $page_id ); } if ( empty( $member_directory_ids ) ) { diff --git a/includes/core/um-actions-form.php b/includes/core/um-actions-form.php index cbf67cec..3c4eae9f 100644 --- a/includes/core/um-actions-form.php +++ b/includes/core/um-actions-form.php @@ -715,6 +715,12 @@ function um_submit_form_errors_hook_( $args ) { } break; + case 'spotify_url': + if ( ! UM()->validation()->is_url( $args[ $key ], 'open.spotify.com' ) ) { + UM()->form()->add_error( $key, sprintf( __( 'Please enter a valid %s URL', 'ultimate-member' ), $array['label'] ) ); + } + break; + case 'telegram_url': if ( ! UM()->validation()->is_url( $args[ $key ], 't.me' ) ) { UM()->form()->add_error( $key, sprintf( __( 'Please enter a valid %s username or profile URL', 'ultimate-member' ), $array['label'] ) ); diff --git a/includes/core/um-actions-profile.php b/includes/core/um-actions-profile.php index c180b3cb..f5cd85a0 100644 --- a/includes/core/um-actions-profile.php +++ b/includes/core/um-actions-profile.php @@ -1725,6 +1725,26 @@ function um_profile_menu( $args ) { */ $nav_link = apply_filters( "um_profile_menu_link_{$id}", $nav_link ); + /** + * Filters a profile menu navigation links' tag attributes. + * + * @since 2.6.3 + * @hook um_profile_menu_link_{$id}_attrs + * + * @param {string} $profile_nav_attrs Link's tag attributes. + * @param {array} $args Profile form arguments. + * + * @return {string} Link's tag attributes. + * + * @example Add a link's tag attributes. + * function um_profile_menu_link_attrs( $profile_nav_attrs ) { + * // your code here + * return $profile_nav_attrs; + * } + * add_filter( 'um_profile_menu_link_{$id}_attrs', 'um_profile_menu_link_attrs', 10, 1 ); + */ + $profile_nav_attrs = apply_filters( "um_profile_menu_link_{$id}_attrs", '', $args ); + $profile_nav_class = ''; if ( ! UM()->options()->get( 'profile_menu_icons' ) ) { $profile_nav_class .= ' without-icon'; @@ -1737,7 +1757,7 @@ function um_profile_menu( $args ) {
options()->get( 'profile_menu_icons' ) ) { ?> + title="" original-title="" > @@ -1748,7 +1768,7 @@ function um_profile_menu( $args ) { + title="" > @@ -1760,7 +1780,7 @@ function um_profile_menu( $args ) { + title="" original-title="" > @@ -1769,7 +1789,7 @@ function um_profile_menu( $args ) { + title="" > 0 ) { ?> diff --git a/includes/core/um-filters-fields.php b/includes/core/um-filters-fields.php index 22609c8d..a64ec2ad 100644 --- a/includes/core/um-filters-fields.php +++ b/includes/core/um-filters-fields.php @@ -77,6 +77,30 @@ function um_profile_field_filter_hook__youtube_video( $value, $data ) { add_filter( 'um_profile_field_filter_hook__youtube_video', 'um_profile_field_filter_hook__youtube_video', 99, 2 ); +/** + * Outputs a spotify iframe + * + * @param $value + * @param $data + * + * @return bool|string + */ +function um_profile_field_filter_hook__spotify( $value, $data ) { + if ( preg_match( '/https:\/\/open.spotify.com\/.*/', $value ) ) { + $url = str_replace( 'open.spotify.com/', 'open.spotify.com/embed/', $value ); + + $value = '
+ +
'; + } else { + return __( 'Invalid Spotify URL', 'ultimate-member' ); + } + + return $value; +} +add_filter( 'um_profile_field_filter_hook__spotify', 'um_profile_field_filter_hook__spotify', 99, 2 ); + + /** * Outputs a vimeo video * diff --git a/includes/um-short-functions.php b/includes/um-short-functions.php index 9c181605..c8951eac 100644 --- a/includes/um-short-functions.php +++ b/includes/um-short-functions.php @@ -1515,10 +1515,10 @@ function um_can_view_field( $data ) { UM()->fields()->set_mode = ''; } - if ( isset( $data['public'] ) && UM()->fields()->set_mode != 'register' ) { + if ( isset( $data['public'] ) && 'register' !== UM()->fields()->set_mode ) { - $can_edit = false; - $current_user_roles = []; + $can_edit = false; + $current_user_roles = array(); if ( is_user_logged_in() ) { $can_edit = UM()->roles()->um_current_user_can( 'edit', um_user( 'ID' ) ); @@ -1563,7 +1563,6 @@ function um_can_view_field( $data ) { $can_view = apply_filters( 'um_can_view_field_custom', $can_view, $data ); break; } - } return apply_filters( 'um_can_view_field', $can_view, $data ); diff --git a/package.json b/package.json index ce1525b8..d08fe57d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,11 @@ { - "name": "roots", - "version": "7.0.3", - "author": "Ben Word ", + "name": "ultimate-member", + "version": "2.6.3", + "author": { + "name": "Ultimate Member", + "email": "team@ultimatemember.com", + "url": "https://ultimatemember.com" + }, "homepage": "http://roots.io", "repository": { "type": "git", @@ -17,7 +21,7 @@ } ], "scripts": { - "postinstall": "node node_modules/bower/bin/bower install && grunt dev", + "build:docs": "rm -rf docs/hooks/ && jsdoc -c hookdoc-conf.json", "build": "npm-run-all build:*", "build:all-blocks": "npm-run-all --parallel build-block-*", "build-block-um-member-directories": "cd includes/blocks/um-member-directories && wp-scripts build src/index.js", @@ -36,6 +40,8 @@ "gulp-sass": "^5.1.0", "gulp-uglify": "^3.0.2", "gulp-clean-css": "^4.3.0", + "jsdoc": "~4.0.2", + "wp-hookdoc": "^0.2.0", "npm-run-all": "^4.1.5", "@wordpress/block-editor": "^11.5.0", "@wordpress/blocks": "^12.5.0", @@ -45,6 +51,7 @@ "@wordpress/server-side-render": "^4.5.0" }, "dependencies": { - "react": "^18.2.0" + "react": "^18.2.0", + "taffydb": "^2.7.3" } } diff --git a/phpdoc.dist.xml b/phpdoc.dist.xml new file mode 100644 index 00000000..2fb02cab --- /dev/null +++ b/phpdoc.dist.xml @@ -0,0 +1,25 @@ + + + Ultimate Member - PHPDoc + + docs/phpdoc + docs/phpdoc/cache + + + latest + + + includes + templates + + + php + + + +