· 8 years ago · May 29, 2018, 09:52 AM
1<?php
2/**
3 * Functions of BuddyPress's Legacy theme.
4 *
5 * @since 1.7.0
6 *
7 * @package BuddyPress
8 * @subpackage BP_Theme_Compat
9 * @version 3.0.0
10 */
11
12// Exit if accessed directly.
13defined( 'ABSPATH' ) || exit;
14
15/** Theme Setup ***************************************************************/
16
17if ( !class_exists( 'BP_Legacy' ) ) :
18
19/**
20 * Loads BuddyPress Legacy Theme functionality.
21 *
22 * This is not a real theme by WordPress standards, and is instead used as the
23 * fallback for any WordPress theme that does not have BuddyPress templates in it.
24 *
25 * To make your custom theme BuddyPress compatible and customize the templates, you
26 * can copy these files into your theme without needing to merge anything
27 * together; BuddyPress should safely handle the rest.
28 *
29 * See @link BP_Theme_Compat() for more.
30 *
31 * @since 1.7.0
32 *
33 * @package BuddyPress
34 * @subpackage BP_Theme_Compat
35 */
36class BP_Legacy extends BP_Theme_Compat {
37
38 /** Functions *************************************************************/
39
40 /**
41 * The main BuddyPress (Legacy) Loader.
42 *
43 * @since 1.7.0
44 *
45 */
46 public function __construct() {
47 parent::start();
48 }
49
50 /**
51 * Component global variables.
52 *
53 * You'll want to customize the values in here, so they match whatever your
54 * needs are.
55 *
56 * @since 1.7.0
57 */
58 protected function setup_globals() {
59 $bp = buddypress();
60 $this->id = 'legacy';
61 $this->name = __( 'BuddyPress Legacy', 'buddypress' );
62 $this->version = bp_get_version();
63 $this->dir = trailingslashit( $bp->themes_dir . '/bp-legacy' );
64 $this->url = trailingslashit( $bp->themes_url . '/bp-legacy' );
65 }
66
67 /**
68 * Setup the theme hooks.
69 *
70 * @since 1.7.0
71 *
72 */
73 protected function setup_actions() {
74
75 // Template Output.
76 add_filter( 'bp_get_activity_action_pre_meta', array( $this, 'secondary_avatars' ), 10, 2 );
77
78 // Filter BuddyPress template hierarchy and look for page templates.
79 add_filter( 'bp_get_buddypress_template', array( $this, 'theme_compat_page_templates' ), 10, 1 );
80
81 /** Scripts ***********************************************************/
82
83 add_action( 'bp_enqueue_scripts', array( $this, 'enqueue_styles' ) ); // Enqueue theme CSS
84 add_action( 'bp_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); // Enqueue theme JS
85 add_filter( 'bp_enqueue_scripts', array( $this, 'localize_scripts' ) ); // Enqueue theme script localization
86
87 /** Body no-js Class **************************************************/
88
89 add_filter( 'body_class', array( $this, 'add_nojs_body_class' ), 20, 1 );
90
91 /** Buttons ***********************************************************/
92
93 if ( ! is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
94 // Register buttons for the relevant component templates
95 // Friends button.
96 if ( bp_is_active( 'friends' ) )
97 add_action( 'bp_member_header_actions', 'bp_add_friend_button', 5 );
98
99 // Activity button.
100 if ( bp_is_active( 'activity' ) && bp_activity_do_mentions() )
101 add_action( 'bp_member_header_actions', 'bp_send_public_message_button', 20 );
102
103 // Messages button.
104 if ( bp_is_active( 'messages' ) )
105 add_action( 'bp_member_header_actions', 'bp_send_private_message_button', 20 );
106
107 // Group buttons.
108 if ( bp_is_active( 'groups' ) ) {
109 add_action( 'bp_group_header_actions', 'bp_group_join_button', 5 );
110 add_action( 'bp_directory_groups_actions', 'bp_group_join_button' );
111 add_action( 'bp_groups_directory_group_filter', 'bp_legacy_theme_group_create_nav', 999 );
112 add_action( 'bp_after_group_admin_content', 'bp_legacy_groups_admin_screen_hidden_input' );
113 add_action( 'bp_before_group_admin_form', 'bp_legacy_theme_group_manage_members_add_search' );
114 }
115
116 // Blog button.
117 if ( bp_is_active( 'blogs' ) ) {
118 add_action( 'bp_directory_blogs_actions', 'bp_blogs_visit_blog_button' );
119 add_action( 'bp_blogs_directory_blog_types', 'bp_legacy_theme_blog_create_nav', 999 );
120 }
121 }
122
123 /** Notices ***********************************************************/
124
125 // Only hook the 'sitewide_notices' overlay if the Sitewide
126 // Notices widget is not in use (to avoid duplicate content).
127 if ( bp_is_active( 'messages' ) && ! is_active_widget( false, false, 'bp_messages_sitewide_notices_widget', true ) ) {
128 add_action( 'wp_footer', array( $this, 'sitewide_notices' ), 9999 );
129 }
130
131 /** Ajax **************************************************************/
132
133 $actions = array(
134
135 // Directory filters.
136 'blogs_filter' => 'bp_legacy_theme_object_template_loader',
137 'forums_filter' => 'bp_legacy_theme_object_template_loader',
138 'groups_filter' => 'bp_legacy_theme_object_template_loader',
139 'members_filter' => 'bp_legacy_theme_object_template_loader',
140 'messages_filter' => 'bp_legacy_theme_messages_template_loader',
141 'invite_filter' => 'bp_legacy_theme_invite_template_loader',
142 'requests_filter' => 'bp_legacy_theme_requests_template_loader',
143
144 // Friends.
145 'accept_friendship' => 'bp_legacy_theme_ajax_accept_friendship',
146 'addremove_friend' => 'bp_legacy_theme_ajax_addremove_friend',
147 'reject_friendship' => 'bp_legacy_theme_ajax_reject_friendship',
148
149 // Activity.
150 'activity_get_older_updates' => 'bp_legacy_theme_activity_template_loader',
151 'activity_mark_fav' => 'bp_legacy_theme_mark_activity_favorite',
152 'activity_mark_unfav' => 'bp_legacy_theme_unmark_activity_favorite',
153 'activity_widget_filter' => 'bp_legacy_theme_activity_template_loader',
154 'delete_activity' => 'bp_legacy_theme_delete_activity',
155 'delete_activity_comment' => 'bp_legacy_theme_delete_activity_comment',
156 'get_single_activity_content' => 'bp_legacy_theme_get_single_activity_content',
157 'new_activity_comment' => 'bp_legacy_theme_new_activity_comment',
158 'post_update' => 'bp_legacy_theme_post_update',
159 'bp_spam_activity' => 'bp_legacy_theme_spam_activity',
160 'bp_spam_activity_comment' => 'bp_legacy_theme_spam_activity',
161
162 // Groups.
163 'groups_invite_user' => 'bp_legacy_theme_ajax_invite_user',
164 'joinleave_group' => 'bp_legacy_theme_ajax_joinleave_group',
165
166 // Messages.
167 'messages_autocomplete_results' => 'bp_legacy_theme_ajax_messages_autocomplete_results',
168 'messages_close_notice' => 'bp_legacy_theme_ajax_close_notice',
169 'messages_delete' => 'bp_legacy_theme_ajax_messages_delete',
170 'messages_markread' => 'bp_legacy_theme_ajax_message_markread',
171 'messages_markunread' => 'bp_legacy_theme_ajax_message_markunread',
172 'messages_send_reply' => 'bp_legacy_theme_ajax_messages_send_reply',
173 );
174
175 // Conditional actions.
176 if ( bp_is_active( 'messages', 'star' ) ) {
177 $actions['messages_star'] = 'bp_legacy_theme_ajax_messages_star_handler';
178 }
179
180 /**
181 * Register all of these AJAX handlers.
182 *
183 * The "wp_ajax_" action is used for logged in users, and "wp_ajax_nopriv_"
184 * executes for users that aren't logged in. This is for backpat with BP <1.6.
185 */
186 foreach( $actions as $name => $function ) {
187 add_action( 'wp_ajax_' . $name, $function );
188 add_action( 'wp_ajax_nopriv_' . $name, $function );
189 }
190
191 add_filter( 'bp_ajax_querystring', 'bp_legacy_theme_ajax_querystring', 10, 2 );
192
193 /** Override **********************************************************/
194
195 /**
196 * Fires after all of the BuddyPress theme compat actions have been added.
197 *
198 * @since 1.7.0
199 *
200 * @param BP_Legacy $this Current BP_Legacy instance.
201 */
202 do_action_ref_array( 'bp_theme_compat_actions', array( &$this ) );
203 }
204
205 /**
206 * Load the theme CSS
207 *
208 * @since 1.7.0
209 * @since 2.3.0 Support custom CSS file named after the current theme or parent theme.
210 *
211 */
212 public function enqueue_styles() {
213 $min = bp_core_get_minified_asset_suffix();
214
215 // Locate the BP stylesheet.
216 $ltr = $this->locate_asset_in_stack( "buddypress{$min}.css", 'css' );
217
218 // LTR.
219 if ( ! is_rtl() && isset( $ltr['location'], $ltr['handle'] ) ) {
220 wp_enqueue_style( $ltr['handle'], $ltr['location'], array(), $this->version, 'screen' );
221
222 if ( $min ) {
223 wp_style_add_data( $ltr['handle'], 'suffix', $min );
224 }
225 }
226
227 // RTL.
228 if ( is_rtl() ) {
229 $rtl = $this->locate_asset_in_stack( "buddypress-rtl{$min}.css", 'css' );
230
231 if ( isset( $rtl['location'], $rtl['handle'] ) ) {
232 $rtl['handle'] = str_replace( '-css', '-css-rtl', $rtl['handle'] ); // Backwards compatibility.
233 wp_enqueue_style( $rtl['handle'], $rtl['location'], array(), $this->version, 'screen' );
234
235 if ( $min ) {
236 wp_style_add_data( $rtl['handle'], 'suffix', $min );
237 }
238 }
239 }
240
241 // Compatibility stylesheets for specific themes.
242 $theme = $this->locate_asset_in_stack( get_template() . "{$min}.css", 'css' );
243 if ( ! is_rtl() && isset( $theme['location'] ) ) {
244 // Use a unique handle.
245 $theme['handle'] = 'bp-' . get_template();
246 wp_enqueue_style( $theme['handle'], $theme['location'], array(), $this->version, 'screen' );
247
248 if ( $min ) {
249 wp_style_add_data( $theme['handle'], 'suffix', $min );
250 }
251 }
252
253 // Compatibility stylesheet for specific themes, RTL-version.
254 if ( is_rtl() ) {
255 $theme_rtl = $this->locate_asset_in_stack( get_template() . "-rtl{$min}.css", 'css' );
256
257 if ( isset( $theme_rtl['location'] ) ) {
258 $theme_rtl['handle'] = $theme['handle'] . '-rtl';
259 wp_enqueue_style( $theme_rtl['handle'], $theme_rtl['location'], array(), $this->version, 'screen' );
260
261 if ( $min ) {
262 wp_style_add_data( $theme_rtl['handle'], 'suffix', $min );
263 }
264 }
265 }
266 }
267
268 /**
269 * Enqueue the required JavaScript files
270 *
271 * @since 1.7.0
272 */
273 public function enqueue_scripts() {
274 $min = bp_core_get_minified_asset_suffix();
275
276 // Locate the BP JS file.
277 $asset = $this->locate_asset_in_stack( "buddypress{$min}.js", 'js' );
278
279 // Enqueue the global JS, if found - AJAX will not work
280 // without it.
281 if ( isset( $asset['location'], $asset['handle'] ) ) {
282 wp_enqueue_script( $asset['handle'], $asset['location'], bp_core_get_js_dependencies(), $this->version );
283 }
284
285 /**
286 * Filters core JavaScript strings for internationalization before AJAX usage.
287 *
288 * @since 2.0.0
289 *
290 * @param array $value Array of key/value pairs for AJAX usage.
291 */
292 $params = apply_filters( 'bp_core_get_js_strings', array(
293 'accepted' => __( 'Accepted', 'buddypress' ),
294 'close' => __( 'Close', 'buddypress' ),
295 'comments' => __( 'comments', 'buddypress' ),
296 'leave_group_confirm' => __( 'Are you sure you want to leave this group?', 'buddypress' ),
297 'mark_as_fav' => __( 'Favorite', 'buddypress' ),
298 'my_favs' => __( 'My Favorites', 'buddypress' ),
299 'rejected' => __( 'Rejected', 'buddypress' ),
300 'remove_fav' => __( 'Remove Favorite', 'buddypress' ),
301 'show_all' => __( 'Show all', 'buddypress' ),
302 'show_all_comments' => __( 'Show all comments for this thread', 'buddypress' ),
303 'show_x_comments' => __( 'Show all comments (%d)', 'buddypress' ),
304 'unsaved_changes' => __( 'Your profile has unsaved changes. If you leave the page, the changes will be lost.', 'buddypress' ),
305 'view' => __( 'View', 'buddypress' ),
306 ) );
307 wp_localize_script( $asset['handle'], 'BP_DTheme', $params );
308
309 // Maybe enqueue comment reply JS.
310 if ( is_singular() && bp_is_blog_page() && get_option( 'thread_comments' ) ) {
311 wp_enqueue_script( 'comment-reply' );
312 }
313
314 // Maybe enqueue password verify JS (register page or user settings page).
315 if ( bp_is_register_page() || ( function_exists( 'bp_is_user_settings_general' ) && bp_is_user_settings_general() ) ) {
316
317 // Locate the Register Page JS file.
318 $asset = $this->locate_asset_in_stack( "password-verify{$min}.js", 'js', 'bp-legacy-password-verify' );
319
320 $dependencies = array_merge( bp_core_get_js_dependencies(), array(
321 'password-strength-meter',
322 ) );
323
324 // Enqueue script.
325 wp_enqueue_script( $asset['handle'] . '-password-verify', $asset['location'], $dependencies, $this->version);
326 }
327
328 // Star private messages.
329 if ( bp_is_active( 'messages', 'star' ) && bp_is_user_messages() ) {
330 wp_localize_script( $asset['handle'], 'BP_PM_Star', array(
331 'strings' => array(
332 'text_unstar' => __( 'Unstar', 'buddypress' ),
333 'text_star' => __( 'Star', 'buddypress' ),
334 'title_unstar' => __( 'Starred', 'buddypress' ),
335 'title_star' => __( 'Not starred', 'buddypress' ),
336 'title_unstar_thread' => __( 'Remove all starred messages in this thread', 'buddypress' ),
337 'title_star_thread' => __( 'Star the first message in this thread', 'buddypress' ),
338 ),
339 'is_single_thread' => (int) bp_is_messages_conversation(),
340 'star_counter' => 0,
341 'unstar_counter' => 0
342 ) );
343 }
344 }
345
346 /**
347 * Get the URL and handle of a web-accessible CSS or JS asset
348 *
349 * We provide two levels of customizability with respect to where CSS
350 * and JS files can be stored: (1) the child theme/parent theme/theme
351 * compat hierarchy, and (2) the "template stack" of /buddypress/css/,
352 * /community/css/, and /css/. In this way, CSS and JS assets can be
353 * overloaded, and default versions provided, in exactly the same way
354 * as corresponding PHP templates.
355 *
356 * We are duplicating some of the logic that is currently found in
357 * bp_locate_template() and the _template_stack() functions. Those
358 * functions were built with PHP templates in mind, and will require
359 * refactoring in order to provide "stack" functionality for assets
360 * that must be accessible both using file_exists() (the file path)
361 * and at a public URI.
362 *
363 * This method is marked private, with the understanding that the
364 * implementation is subject to change or removal in an upcoming
365 * release, in favor of a unified _template_stack() system. Plugin
366 * and theme authors should not attempt to use what follows.
367 *
368 * @since 1.8.0
369 * @param string $file A filename like buddypress.css.
370 * @param string $type Optional. Either "js" or "css" (the default).
371 * @param string $script_handle Optional. If set, used as the script name in `wp_enqueue_script`.
372 * @return array An array of data for the wp_enqueue_* function:
373 * 'handle' (eg 'bp-child-css') and a 'location' (the URI of the
374 * asset)
375 */
376 private function locate_asset_in_stack( $file, $type = 'css', $script_handle = '' ) {
377 $locations = array();
378
379 // Ensure the assets can be located when running from /src/.
380 if ( defined( 'BP_SOURCE_SUBDIRECTORY' ) && BP_SOURCE_SUBDIRECTORY === 'src' ) {
381 $file = str_replace( '.min', '', $file );
382 }
383
384 // No need to check child if template == stylesheet.
385 if ( is_child_theme() ) {
386 $locations[] = array(
387 'type' => 'bp-child',
388 'dir' => get_stylesheet_directory(),
389 'uri' => get_stylesheet_directory_uri(),
390 'file' => $file,
391 );
392
393 $locations[] = array(
394 'type' => 'bp-child',
395 'dir' => get_stylesheet_directory(),
396 'uri' => get_stylesheet_directory_uri(),
397 'file' => str_replace( '.min', '', $file ),
398 );
399 }
400
401 $locations[] = array(
402 'type' => 'bp-parent',
403 'dir' => get_template_directory(),
404 'uri' => get_template_directory_uri(),
405 'file' => str_replace( '.min', '', $file ),
406 );
407
408 $locations[] = array(
409 'type' => 'bp-legacy',
410 'dir' => bp_get_theme_compat_dir(),
411 'uri' => bp_get_theme_compat_url(),
412 'file' => $file,
413 );
414
415 // Subdirectories within the top-level $locations directories.
416 $subdirs = array(
417 'buddypress/' . $type,
418 'community/' . $type,
419 $type,
420 );
421
422 $retval = array();
423
424 foreach ( $locations as $location ) {
425 foreach ( $subdirs as $subdir ) {
426 if ( file_exists( trailingslashit( $location['dir'] ) . trailingslashit( $subdir ) . $location['file'] ) ) {
427 $retval['location'] = trailingslashit( $location['uri'] ) . trailingslashit( $subdir ) . $location['file'];
428 $retval['handle'] = ( $script_handle ) ? $script_handle : "{$location['type']}-{$type}";
429
430 break 2;
431 }
432 }
433 }
434
435 return $retval;
436 }
437
438 /**
439 * Adds the no-js class to the body tag.
440 *
441 * This function ensures that the <body> element will have the 'no-js' class by default. If you're
442 * using JavaScript for some visual functionality in your theme, and you want to provide noscript
443 * support, apply those styles to body.no-js.
444 *
445 * The no-js class is removed by the JavaScript created in buddypress.js.
446 *
447 * @since 1.7.0
448 *
449 * @param array $classes Array of classes to append to body tag.
450 * @return array $classes
451 */
452 public function add_nojs_body_class( $classes ) {
453 if ( ! in_array( 'no-js', $classes ) )
454 $classes[] = 'no-js';
455
456 return array_unique( $classes );
457 }
458
459 /**
460 * Load localizations for topic script.
461 *
462 * These localizations require information that may not be loaded even by init.
463 *
464 * @since 1.7.0
465 */
466 public function localize_scripts() {
467 }
468
469 /**
470 * Outputs sitewide notices markup in the footer.
471 *
472 * @since 1.7.0
473 *
474 * @see https://buddypress.trac.wordpress.org/ticket/4802
475 */
476 public function sitewide_notices() {
477 // Do not show notices if user is not logged in.
478 if ( ! is_user_logged_in() )
479 return;
480
481 // Add a class to determine if the admin bar is on or not.
482 $class = did_action( 'admin_bar_menu' ) ? 'admin-bar-on' : 'admin-bar-off';
483
484 echo '<div id="sitewide-notice" class="' . $class . '">';
485 bp_message_get_notices();
486 echo '</div>';
487 }
488
489 /**
490 * Add secondary avatar image to this activity stream's record, if supported.
491 *
492 * @since 1.7.0
493 *
494 * @param string $action The text of this activity.
495 * @param BP_Activity_Activity $activity Activity object.
496 * @return string
497 */
498 function secondary_avatars( $action, $activity ) {
499 switch ( $activity->component ) {
500 case 'groups' :
501 case 'friends' :
502 // Only insert avatar if one exists.
503 if ( $secondary_avatar = bp_get_activity_secondary_avatar() ) {
504 $reverse_content = strrev( $action );
505 $position = strpos( $reverse_content, 'a<' );
506 $action = substr_replace( $action, $secondary_avatar, -$position - 2, 0 );
507 }
508 break;
509 }
510
511 return $action;
512 }
513
514 /**
515 * Filter the default theme compatibility root template hierarchy, and prepend
516 * a page template to the front if it's set.
517 *
518 * @see https://buddypress.trac.wordpress.org/ticket/6065
519 *
520 * @since 2.2.0
521 *
522 * @param array $templates Array of templates.
523 * to use the defined page template for component's directory and its single items
524 * @return array
525 */
526 public function theme_compat_page_templates( $templates = array() ) {
527
528 /**
529 * Filters whether or not we are looking at a directory to determine if to return early.
530 *
531 * @since 2.2.0
532 *
533 * @param bool $value Whether or not we are viewing a directory.
534 */
535 if ( true === (bool) apply_filters( 'bp_legacy_theme_compat_page_templates_directory_only', ! bp_is_directory() ) ) {
536 return $templates;
537 }
538
539 // No page ID yet.
540 $page_id = 0;
541
542 // Get the WordPress Page ID for the current view.
543 foreach ( (array) buddypress()->pages as $component => $bp_page ) {
544
545 // Handles the majority of components.
546 if ( bp_is_current_component( $component ) ) {
547 $page_id = (int) $bp_page->id;
548 }
549
550 // Stop if not on a user page.
551 if ( ! bp_is_user() && ! empty( $page_id ) ) {
552 break;
553 }
554
555 // The Members component requires an explicit check due to overlapping components.
556 if ( bp_is_user() && ( 'members' === $component ) ) {
557 $page_id = (int) $bp_page->id;
558 break;
559 }
560 }
561
562 // Bail if no directory page set.
563 if ( 0 === $page_id ) {
564 return $templates;
565 }
566
567 // Check for page template.
568 $page_template = get_page_template_slug( $page_id );
569
570 // Add it to the beginning of the templates array so it takes precedence
571 // over the default hierarchy.
572 if ( ! empty( $page_template ) ) {
573
574 /**
575 * Check for existence of template before adding it to template
576 * stack to avoid accidentally including an unintended file.
577 *
578 * @see: https://buddypress.trac.wordpress.org/ticket/6190
579 */
580 if ( '' !== locate_template( $page_template ) ) {
581 array_unshift( $templates, $page_template );
582 }
583 }
584
585 return $templates;
586 }
587}
588new BP_Legacy();
589endif;
590
591/**
592 * Add the Create a Group button to the Groups directory title.
593 *
594 * The bp-legacy puts the Create a Group button into the page title, to mimic
595 * the behavior of bp-default.
596 *
597 * @since 2.0.0
598 * @todo Deprecate
599 *
600 * @param string $title Groups directory title.
601 * @return string
602 */
603function bp_legacy_theme_group_create_button( $title ) {
604 return $title . ' ' . bp_get_group_create_button();
605}
606
607/**
608 * Add the Create a Group nav to the Groups directory navigation.
609 *
610 * The bp-legacy puts the Create a Group nav at the last position of
611 * the Groups directory navigation.
612 *
613 * @since 2.2.0
614 *
615 */
616function bp_legacy_theme_group_create_nav() {
617 bp_group_create_nav_item();
618}
619
620/**
621 * Renders the group ID hidden input on group admin screens.
622 *
623 * @since 2.7.0
624 *
625 * @return string|null html
626 */
627function bp_legacy_groups_admin_screen_hidden_input() {
628 ?>
629 <input type="hidden" name="group-id" id="group-id" value="<?php bp_group_id(); ?>" />
630 <?php
631}
632
633/**
634 * Add the Create a Site button to the Sites directory title.
635 *
636 * The bp-legacy puts the Create a Site button into the page title, to mimic
637 * the behavior of bp-default.
638 *
639 * @since 2.0.0
640 * @todo Deprecate
641 *
642 * @param string $title Sites directory title.
643 * @return string
644 */
645function bp_legacy_theme_blog_create_button( $title ) {
646 return $title . ' ' . bp_get_blog_create_button();
647}
648
649/**
650 * Add the Create a Site nav to the Sites directory navigation.
651 *
652 * The bp-legacy puts the Create a Site nav at the last position of
653 * the Sites directory navigation.
654 *
655 * @since 2.2.0
656 *
657 */
658function bp_legacy_theme_blog_create_nav() {
659 bp_blog_create_nav_item();
660}
661
662/**
663 * This function looks scarier than it actually is. :)
664 * Each object loop (activity/members/groups/blogs/forums) contains default
665 * parameters to show specific information based on the page we are currently
666 * looking at.
667 *
668 * The following function will take into account any cookies set in the JS and
669 * allow us to override the parameters sent. That way we can change the results
670 * returned without reloading the page.
671 *
672 * By using cookies we can also make sure that user settings are retained
673 * across page loads.
674 *
675 * @since 1.2.0
676 *
677 * @param string $query_string Query string for the current request.
678 * @param string $object Object for cookie.
679 * @return string Query string for the component loops.
680 */
681function bp_legacy_theme_ajax_querystring( $query_string, $object ) {
682 if ( empty( $object ) )
683 return '';
684
685 // Set up the cookies passed on this AJAX request. Store a local var to avoid conflicts.
686 if ( ! empty( $_POST['cookie'] ) ) {
687 $_BP_COOKIE = wp_parse_args( str_replace( '; ', '&', urldecode( $_POST['cookie'] ) ) );
688 } else {
689 $_BP_COOKIE = &$_COOKIE;
690 }
691
692 $qs = array();
693
694 /**
695 * Check if any cookie values are set. If there are then override the
696 * default params passed to the template loop.
697 */
698
699 // Activity stream filtering on action.
700 if ( ! empty( $_BP_COOKIE['bp-' . $object . '-filter'] ) && '-1' != $_BP_COOKIE['bp-' . $object . '-filter'] ) {
701 $qs[] = 'type=' . urlencode( $_BP_COOKIE['bp-' . $object . '-filter'] );
702
703 if ( bp_is_active( 'activity' ) ) {
704 $actions = bp_activity_get_actions_for_context();
705
706 // Handle multiple actions (eg. 'friendship_accepted,friendship_created')
707 $action_filter = explode( ',', $_BP_COOKIE['bp-' . $object . '-filter'] );
708
709 // See if action filter matches registered actions. If so, add it to qs.
710 if ( ! array_diff( $action_filter, wp_list_pluck( $actions, 'key' ) ) ) {
711 $qs[] = 'action=' . join( ',', $action_filter );
712 }
713 }
714 }
715
716 if ( ! empty( $_BP_COOKIE['bp-' . $object . '-scope'] ) ) {
717 if ( 'personal' == $_BP_COOKIE['bp-' . $object . '-scope'] ) {
718 $user_id = ( bp_displayed_user_id() ) ? bp_displayed_user_id() : bp_loggedin_user_id();
719 $qs[] = 'user_id=' . $user_id;
720 }
721
722 // Activity stream scope only on activity directory.
723 if ( 'all' != $_BP_COOKIE['bp-' . $object . '-scope'] && ! bp_displayed_user_id() && ! bp_is_single_item() )
724 $qs[] = 'scope=' . urlencode( $_BP_COOKIE['bp-' . $object . '-scope'] );
725 }
726
727 // If page and search_terms have been passed via the AJAX post request, use those.
728 if ( ! empty( $_POST['page'] ) && '-1' != $_POST['page'] )
729 $qs[] = 'page=' . absint( $_POST['page'] );
730
731 // Excludes activity just posted and avoids duplicate ids.
732 if ( ! empty( $_POST['exclude_just_posted'] ) ) {
733 $just_posted = wp_parse_id_list( $_POST['exclude_just_posted'] );
734 $qs[] = 'exclude=' . implode( ',', $just_posted );
735 }
736
737 // To get newest activities.
738 if ( ! empty( $_POST['offset'] ) ) {
739 $qs[] = 'offset=' . intval( $_POST['offset'] );
740 }
741
742 $object_search_text = bp_get_search_default_text( $object );
743 if ( ! empty( $_POST['search_terms'] ) && $object_search_text != $_POST['search_terms'] && 'false' != $_POST['search_terms'] && 'undefined' != $_POST['search_terms'] )
744 $qs[] = 'search_terms=' . urlencode( $_POST['search_terms'] );
745
746 // Now pass the querystring to override default values.
747 $query_string = empty( $qs ) ? '' : join( '&', (array) $qs );
748
749 $object_filter = '';
750 if ( isset( $_BP_COOKIE['bp-' . $object . '-filter'] ) )
751 $object_filter = $_BP_COOKIE['bp-' . $object . '-filter'];
752
753 $object_scope = '';
754 if ( isset( $_BP_COOKIE['bp-' . $object . '-scope'] ) )
755 $object_scope = $_BP_COOKIE['bp-' . $object . '-scope'];
756
757 $object_page = '';
758 if ( isset( $_BP_COOKIE['bp-' . $object . '-page'] ) )
759 $object_page = $_BP_COOKIE['bp-' . $object . '-page'];
760
761 $object_search_terms = '';
762 if ( isset( $_BP_COOKIE['bp-' . $object . '-search-terms'] ) )
763 $object_search_terms = $_BP_COOKIE['bp-' . $object . '-search-terms'];
764
765 $object_extras = '';
766 if ( isset( $_BP_COOKIE['bp-' . $object . '-extras'] ) )
767 $object_extras = $_BP_COOKIE['bp-' . $object . '-extras'];
768
769 /**
770 * Filters the AJAX query string for the component loops.
771 *
772 * @since 1.7.0
773 *
774 * @param string $query_string The query string we are working with.
775 * @param string $object The type of page we are on.
776 * @param string $object_filter The current object filter.
777 * @param string $object_scope The current object scope.
778 * @param string $object_page The current object page.
779 * @param string $object_search_terms The current object search terms.
780 * @param string $object_extras The current object extras.
781 */
782 return apply_filters( 'bp_legacy_theme_ajax_querystring', $query_string, $object, $object_filter, $object_scope, $object_page, $object_search_terms, $object_extras );
783}
784
785/**
786 * Load the template loop for the current object.
787 *
788 * @since 1.2.0
789 *
790 * @return string|null Prints template loop for the specified object
791 */
792function bp_legacy_theme_object_template_loader() {
793 if ( ! bp_is_post_request() ) {
794 return;
795 }
796
797 // Bail if no object passed.
798 if ( empty( $_POST['object'] ) ) {
799 return;
800 }
801
802 // Sanitize the object.
803 $object = sanitize_title( $_POST['object'] );
804
805 // Bail if object is not an active component to prevent arbitrary file inclusion.
806 if ( ! bp_is_active( $object ) ) {
807 return;
808 }
809
810 /**
811 * AJAX requests happen too early to be seen by bp_update_is_directory()
812 * so we do it manually here to ensure templates load with the correct
813 * context. Without this check, templates will load the 'single' version
814 * of themselves rather than the directory version.
815 */
816 if ( ! bp_current_action() )
817 bp_update_is_directory( true, bp_current_component() );
818
819 // The template part can be overridden by the calling JS function.
820 if ( ! empty( $_POST['template'] ) && 'groups/single/members' === $_POST['template'] ) {
821 $template_part = 'groups/single/members.php';
822 } else {
823 $template_part = $object . '/' . $object . '-loop.php';
824 }
825
826 $template_path = bp_locate_template( array( $template_part ), false );
827
828 $template_path = apply_filters( 'bp_legacy_object_template_path', $template_path );
829
830 load_template( $template_path );
831 exit();
832}
833
834/**
835 * Load messages template loop when searched on the private message page
836 *
837 * @since 1.6.0
838 *
839 * @return string|null Prints template loop for the Messages component.
840 */
841function bp_legacy_theme_messages_template_loader() {
842 bp_get_template_part( 'members/single/messages/messages-loop' );
843 exit();
844}
845
846/**
847 * Load group invitations loop to handle pagination requests sent via AJAX.
848 *
849 * @since 2.0.0
850 */
851function bp_legacy_theme_invite_template_loader() {
852 bp_get_template_part( 'groups/single/invites-loop' );
853 exit();
854}
855
856/**
857 * Load group membership requests loop to handle pagination requests sent via AJAX.
858 *
859 * @since 2.0.0
860 */
861function bp_legacy_theme_requests_template_loader() {
862 bp_get_template_part( 'groups/single/requests-loop' );
863 exit();
864}
865
866/**
867 * Load the activity loop template when activity is requested via AJAX.
868 *
869 * @since 1.2.0
870 *
871 * @return string|null JSON object containing 'contents' (output of the template loop
872 * for the Activity component) and 'feed_url' (URL to the relevant RSS feed).
873 */
874function bp_legacy_theme_activity_template_loader() {
875 if ( ! bp_is_post_request() ) {
876 return;
877 }
878
879 $scope = '';
880 if ( ! empty( $_POST['scope'] ) )
881 $scope = $_POST['scope'];
882
883 // We need to calculate and return the feed URL for each scope.
884 switch ( $scope ) {
885 case 'friends':
886 $feed_url = bp_loggedin_user_domain() . bp_get_activity_slug() . '/friends/feed/';
887 break;
888 case 'groups':
889 $feed_url = bp_loggedin_user_domain() . bp_get_activity_slug() . '/groups/feed/';
890 break;
891 case 'favorites':
892 $feed_url = bp_loggedin_user_domain() . bp_get_activity_slug() . '/favorites/feed/';
893 break;
894 case 'mentions':
895 $feed_url = bp_loggedin_user_domain() . bp_get_activity_slug() . '/mentions/feed/';
896
897 if ( isset( $_POST['_wpnonce_activity_filter'] ) && wp_verify_nonce( wp_unslash( $_POST['_wpnonce_activity_filter'] ), 'activity_filter' ) ) {
898 bp_activity_clear_new_mentions( bp_loggedin_user_id() );
899 }
900
901 break;
902 default:
903 $feed_url = home_url( bp_get_activity_root_slug() . '/feed/' );
904 break;
905 }
906
907 // Buffer the loop in the template to a var for JS to spit out.
908 ob_start();
909 bp_get_template_part( 'activity/activity-loop' );
910 $result['contents'] = ob_get_contents();
911
912 /**
913 * Filters the feed URL for when activity is requested via AJAX.
914 *
915 * @since 1.7.0
916 *
917 * @param string $feed_url URL for the feed to be used.
918 * @param string $scope Scope for the activity request.
919 */
920 $result['feed_url'] = apply_filters( 'bp_legacy_theme_activity_feed_url', $feed_url, $scope );
921 ob_end_clean();
922
923 exit( json_encode( $result ) );
924}
925
926/**
927 * Processes Activity updates received via a POST request.
928 *
929 * @since 1.2.0
930 *
931 * @return string|null HTML
932 */
933function bp_legacy_theme_post_update() {
934 $bp = buddypress();
935
936 if ( ! bp_is_post_request() ) {
937 return;
938 }
939
940 // Check the nonce.
941 check_admin_referer( 'post_update', '_wpnonce_post_update' );
942
943 if ( ! is_user_logged_in() )
944 exit( '-1' );
945
946 if ( empty( $_POST['content'] ) )
947 exit( '-1<div id="message" class="error bp-ajax-message"><p>' . __( 'Please enter some content to post.', 'buddypress' ) . '</p></div>' );
948
949 $activity_id = 0;
950 $item_id = 0;
951 $object = '';
952
953
954 // Try to get the item id from posted variables.
955 if ( ! empty( $_POST['item_id'] ) ) {
956 $item_id = (int) $_POST['item_id'];
957 }
958
959 // Try to get the object from posted variables.
960 if ( ! empty( $_POST['object'] ) ) {
961 $object = sanitize_key( $_POST['object'] );
962
963 // If the object is not set and we're in a group, set the item id and the object
964 } elseif ( bp_is_group() ) {
965 $item_id = bp_get_current_group_id();
966 $object = 'groups';
967 }
968
969 if ( ! $object && bp_is_active( 'activity' ) ) {
970 $activity_id = bp_activity_post_update( array( 'content' => $_POST['content'], 'error_type' => 'wp_error' ) );
971
972 } elseif ( 'groups' === $object ) {
973 if ( $item_id && bp_is_active( 'groups' ) )
974 $activity_id = groups_post_update( array( 'content' => $_POST['content'], 'group_id' => $item_id, 'error_type' => 'wp_error' ) );
975
976 } else {
977
978 /** This filter is documented in bp-activity/bp-activity-actions.php */
979 $activity_id = apply_filters( 'bp_activity_custom_update', false, $object, $item_id, $_POST['content'] );
980 }
981
982 if ( false === $activity_id ) {
983 exit( '-1<div id="message" class="error bp-ajax-message"><p>' . __( 'There was a problem posting your update. Please try again.', 'buddypress' ) . '</p></div>' );
984 } elseif ( is_wp_error( $activity_id ) && $activity_id->get_error_code() ) {
985 exit( '-1<div id="message" class="error bp-ajax-message"><p>' . $activity_id->get_error_message() . '</p></div>' );
986 }
987
988 $last_recorded = ! empty( $_POST['since'] ) ? date( 'Y-m-d H:i:s', intval( $_POST['since'] ) ) : 0;
989 if ( $last_recorded ) {
990 $activity_args = array( 'since' => $last_recorded );
991 $bp->activity->last_recorded = $last_recorded;
992 add_filter( 'bp_get_activity_css_class', 'bp_activity_newest_class', 10, 1 );
993 } else {
994 $activity_args = array( 'include' => $activity_id );
995 }
996
997 if ( bp_has_activities ( $activity_args ) ) {
998 while ( bp_activities() ) {
999 bp_the_activity();
1000 bp_get_template_part( 'activity/entry' );
1001 }
1002 }
1003
1004 if ( ! empty( $last_recorded ) ) {
1005 remove_filter( 'bp_get_activity_css_class', 'bp_activity_newest_class', 10 );
1006 }
1007
1008 exit;
1009}
1010
1011/**
1012 * Posts new Activity comments received via a POST request.
1013 *
1014 * @since 1.2.0
1015 *
1016 * @global BP_Activity_Template $activities_template
1017 *
1018 * @return string|null HTML
1019 */
1020function bp_legacy_theme_new_activity_comment() {
1021 global $activities_template;
1022
1023 $bp = buddypress();
1024
1025 if ( ! bp_is_post_request() ) {
1026 return;
1027 }
1028
1029 // Check the nonce.
1030 check_admin_referer( 'new_activity_comment', '_wpnonce_new_activity_comment' );
1031
1032 if ( ! is_user_logged_in() ) {
1033 exit( '-1' );
1034 }
1035
1036 $feedback = __( 'There was an error posting your reply. Please try again.', 'buddypress' );
1037
1038 if ( empty( $_POST['content'] ) ) {
1039 exit( '-1<div id="message" class="error bp-ajax-message"><p>' . esc_html__( 'Please do not leave the comment area blank.', 'buddypress' ) . '</p></div>' );
1040 }
1041
1042 if ( empty( $_POST['form_id'] ) || empty( $_POST['comment_id'] ) || ! is_numeric( $_POST['form_id'] ) || ! is_numeric( $_POST['comment_id'] ) ) {
1043 exit( '-1<div id="message" class="error bp-ajax-message"><p>' . esc_html( $feedback ) . '</p></div>' );
1044 }
1045
1046 $comment_id = bp_activity_new_comment( array(
1047 'activity_id' => $_POST['form_id'],
1048 'content' => $_POST['content'],
1049 'parent_id' => $_POST['comment_id'],
1050 'error_type' => 'wp_error'
1051 ) );
1052
1053 if ( is_wp_error( $comment_id ) ) {
1054 exit( '-1<div id="message" class="error bp-ajax-message"><p>' . esc_html( $comment_id->get_error_message() ) . '</p></div>' );
1055 }
1056
1057 // Load the new activity item into the $activities_template global.
1058 bp_has_activities( 'display_comments=stream&hide_spam=false&show_hidden=true&include=' . $comment_id );
1059
1060 // Swap the current comment with the activity item we just loaded.
1061 if ( isset( $activities_template->activities[0] ) ) {
1062 $activities_template->activity = new stdClass();
1063 $activities_template->activity->id = $activities_template->activities[0]->item_id;
1064 $activities_template->activity->current_comment = $activities_template->activities[0];
1065
1066 // Because the whole tree has not been loaded, we manually
1067 // determine depth.
1068 $depth = 1;
1069 $parent_id = (int) $activities_template->activities[0]->secondary_item_id;
1070 while ( $parent_id !== (int) $activities_template->activities[0]->item_id ) {
1071 $depth++;
1072 $p_obj = new BP_Activity_Activity( $parent_id );
1073 $parent_id = (int) $p_obj->secondary_item_id;
1074 }
1075 $activities_template->activity->current_comment->depth = $depth;
1076 }
1077
1078 // Get activity comment template part.
1079 bp_get_template_part( 'activity/comment' );
1080
1081 unset( $activities_template );
1082 exit;
1083}
1084
1085/**
1086 * Deletes an Activity item received via a POST request.
1087 *
1088 * @since 1.2.0
1089 *
1090 * @return mixed String on error, void on success.
1091 */
1092function bp_legacy_theme_delete_activity() {
1093 if ( ! bp_is_post_request() ) {
1094 return;
1095 }
1096
1097 // Check the nonce.
1098 check_admin_referer( 'bp_activity_delete_link' );
1099
1100 if ( ! is_user_logged_in() )
1101 exit( '-1' );
1102
1103 if ( empty( $_POST['id'] ) || ! is_numeric( $_POST['id'] ) )
1104 exit( '-1' );
1105
1106 $activity = new BP_Activity_Activity( (int) $_POST['id'] );
1107
1108 // Check access.
1109 if ( ! bp_activity_user_can_delete( $activity ) )
1110 exit( '-1' );
1111
1112 /** This action is documented in bp-activity/bp-activity-actions.php */
1113 do_action( 'bp_activity_before_action_delete_activity', $activity->id, $activity->user_id );
1114
1115 if ( ! bp_activity_delete( array( 'id' => $activity->id, 'user_id' => $activity->user_id ) ) )
1116 exit( '-1<div id="message" class="error bp-ajax-message"><p>' . __( 'There was a problem when deleting. Please try again.', 'buddypress' ) . '</p></div>' );
1117
1118 /** This action is documented in bp-activity/bp-activity-actions.php */
1119 do_action( 'bp_activity_action_delete_activity', $activity->id, $activity->user_id );
1120 exit;
1121}
1122
1123/**
1124 * Deletes an Activity comment received via a POST request.
1125 *
1126 * @since 1.2.0
1127 *
1128 * @return mixed String on error, void on success.
1129 */
1130function bp_legacy_theme_delete_activity_comment() {
1131 if ( ! bp_is_post_request() ) {
1132 return;
1133 }
1134
1135 // Check the nonce.
1136 check_admin_referer( 'bp_activity_delete_link' );
1137
1138 if ( empty( $_POST['id'] ) || ! is_numeric( $_POST['id'] ) ) {
1139 exit( '-1' );
1140 }
1141
1142 if ( ! is_user_logged_in() ) {
1143 exit( '-1' );
1144 }
1145
1146 $comment = new BP_Activity_Activity( $_POST['id'] );
1147
1148 // Check access.
1149 if ( ! bp_current_user_can( 'bp_moderate' ) && $comment->user_id != bp_loggedin_user_id() )
1150 exit( '-1' );
1151
1152 /** This action is documented in bp-activity/bp-activity-actions.php */
1153 do_action( 'bp_activity_before_action_delete_activity', $_POST['id'], $comment->user_id );
1154
1155 if ( ! bp_activity_delete_comment( $comment->item_id, $comment->id ) )
1156 exit( '-1<div id="message" class="error bp-ajax-message"><p>' . __( 'There was a problem when deleting. Please try again.', 'buddypress' ) . '</p></div>' );
1157
1158 /** This action is documented in bp-activity/bp-activity-actions.php */
1159 do_action( 'bp_activity_action_delete_activity', $_POST['id'], $comment->user_id );
1160 exit;
1161}
1162
1163/**
1164 * AJAX spam an activity item or comment.
1165 *
1166 * @since 1.6.0
1167 *
1168 * @return mixed String on error, void on success.
1169 */
1170function bp_legacy_theme_spam_activity() {
1171 $bp = buddypress();
1172
1173 if ( ! bp_is_post_request() ) {
1174 return;
1175 }
1176
1177 // Check that user is logged in, Activity Streams are enabled, and Akismet is present.
1178 if ( ! is_user_logged_in() || ! bp_is_active( 'activity' ) || empty( $bp->activity->akismet ) )
1179 exit( '-1' );
1180
1181 // Check an item ID was passed.
1182 if ( empty( $_POST['id'] ) || ! is_numeric( $_POST['id'] ) )
1183 exit( '-1' );
1184
1185 // Is the current user allowed to spam items?
1186 if ( ! bp_activity_user_can_mark_spam() )
1187 exit( '-1' );
1188
1189 // Load up the activity item.
1190 $activity = new BP_Activity_Activity( (int) $_POST['id'] );
1191 if ( empty( $activity->component ) )
1192 exit( '-1' );
1193
1194 // Check nonce.
1195 check_admin_referer( 'bp_activity_akismet_spam_' . $activity->id );
1196
1197 /** This action is documented in bp-activity/bp-activity-actions.php */
1198 do_action( 'bp_activity_before_action_spam_activity', $activity->id, $activity );
1199
1200 // Mark as spam.
1201 bp_activity_mark_as_spam( $activity );
1202 $activity->save();
1203
1204 /** This action is documented in bp-activity/bp-activity-actions.php */
1205 do_action( 'bp_activity_action_spam_activity', $activity->id, $activity->user_id );
1206 exit;
1207}
1208
1209/**
1210 * Mark an activity as a favourite via a POST request.
1211 *
1212 * @since 1.2.0
1213 *
1214 * @return string|null HTML
1215 */
1216function bp_legacy_theme_mark_activity_favorite() {
1217 // Bail if not a POST action.
1218 if ( ! bp_is_post_request() ) {
1219 return;
1220 }
1221
1222 if ( ! isset( $_POST['nonce'] ) ) {
1223 return;
1224 }
1225
1226 // Either the 'mark' or 'unmark' nonce is accepted, for backward compatibility.
1227 $nonce = wp_unslash( $_POST['nonce'] );
1228 if ( ! wp_verify_nonce( $nonce, 'mark_favorite' ) && ! wp_verify_nonce( $nonce, 'unmark_favorite' ) ) {
1229 return;
1230 }
1231
1232 if ( bp_activity_add_user_favorite( $_POST['id'] ) )
1233 _e( 'Remove Favorite', 'buddypress' );
1234 else
1235 _e( 'Favorite', 'buddypress' );
1236
1237 exit;
1238}
1239
1240/**
1241 * Un-favourite an activity via a POST request.
1242 *
1243 * @since 1.2.0
1244 *
1245 * @return string|null HTML
1246 */
1247function bp_legacy_theme_unmark_activity_favorite() {
1248 if ( ! bp_is_post_request() ) {
1249 return;
1250 }
1251
1252 if ( ! isset( $_POST['nonce'] ) ) {
1253 return;
1254 }
1255
1256 // Either the 'mark' or 'unmark' nonce is accepted, for backward compatibility.
1257 $nonce = wp_unslash( $_POST['nonce'] );
1258 if ( ! wp_verify_nonce( $nonce, 'mark_favorite' ) && ! wp_verify_nonce( $nonce, 'unmark_favorite' ) ) {
1259 return;
1260 }
1261
1262 if ( bp_activity_remove_user_favorite( $_POST['id'] ) )
1263 _e( 'Favorite', 'buddypress' );
1264 else
1265 _e( 'Remove Favorite', 'buddypress' );
1266
1267 exit;
1268}
1269
1270/**
1271 * Fetches an activity's full, non-excerpted content via a POST request.
1272 * Used for the 'Read More' link on long activity items.
1273 *
1274 * @since 1.5.0
1275 *
1276 * @return string|null HTML
1277 */
1278function bp_legacy_theme_get_single_activity_content() {
1279 if ( ! bp_is_post_request() ) {
1280 return;
1281 }
1282
1283 $activity_array = bp_activity_get_specific( array(
1284 'activity_ids' => $_POST['activity_id'],
1285 'display_comments' => 'stream'
1286 ) );
1287
1288 $activity = ! empty( $activity_array['activities'][0] ) ? $activity_array['activities'][0] : false;
1289
1290 if ( empty( $activity ) )
1291 exit; // @todo: error?
1292
1293 /**
1294 * Fires before the return of an activity's full, non-excerpted content via a POST request.
1295 *
1296 * @since 1.7.0
1297 *
1298 * @param string $activity Activity content. Passed by reference.
1299 */
1300 do_action_ref_array( 'bp_legacy_theme_get_single_activity_content', array( &$activity ) );
1301
1302 // Activity content retrieved through AJAX should run through normal filters, but not be truncated.
1303 remove_filter( 'bp_get_activity_content_body', 'bp_activity_truncate_entry', 5 );
1304
1305 /** This filter is documented in bp-activity/bp-activity-template.php */
1306 $content = apply_filters_ref_array( 'bp_get_activity_content_body', array( $activity->content, &$activity ) );
1307
1308 exit( $content );
1309}
1310
1311/**
1312 * Invites a friend to join a group via a POST request.
1313 *
1314 * @since 1.2.0
1315 *
1316 * @todo Audit return types
1317 */
1318function bp_legacy_theme_ajax_invite_user() {
1319 if ( ! bp_is_post_request() ) {
1320 return;
1321 }
1322
1323 check_ajax_referer( 'groups_invite_uninvite_user' );
1324
1325 if ( ! $_POST['friend_id'] || ! $_POST['friend_action'] || ! $_POST['group_id'] )
1326 return;
1327
1328 if ( ! bp_groups_user_can_send_invites( $_POST['group_id'] ) )
1329 return;
1330
1331 $group_id = (int) $_POST['group_id'];
1332 $friend_id = (int) $_POST['friend_id'];
1333
1334 if ( 'invite' == $_POST['friend_action'] ) {
1335 if ( ! friends_check_friendship( bp_loggedin_user_id(), $_POST['friend_id'] ) ) {
1336 return;
1337 }
1338
1339 $group = groups_get_group( $group_id );
1340
1341 // Users who have previously requested membership do not need
1342 // another invitation created for them.
1343 if ( groups_check_for_membership_request( $friend_id, $group_id ) ) {
1344 $user_status = 'is_pending';
1345
1346 // Create the user invitation.
1347 } elseif ( groups_invite_user( array( 'user_id' => $friend_id, 'group_id' => $group_id ) ) ) {
1348 $user_status = 'is_invited';
1349
1350 // Miscellaneous failure.
1351 } else {
1352 return;
1353 }
1354
1355 $user = new BP_Core_User( $friend_id );
1356
1357 $uninvite_url = bp_is_current_action( 'create' )
1358 ? bp_get_groups_directory_permalink() . 'create/step/group-invites/?user_id=' . $friend_id
1359 : bp_get_group_permalink( $group ) . 'send-invites/remove/' . $friend_id;
1360
1361 echo '<li id="uid-' . esc_attr( $user->id ) . '">';
1362 echo $user->avatar_thumb;
1363 echo '<h4>' . $user->user_link . '</h4>';
1364 echo '<span class="activity">' . esc_attr( $user->last_active ) . '</span>';
1365 echo '<div class="action">
1366 <a class="button remove" href="' . wp_nonce_url( $uninvite_url, 'groups_invite_uninvite_user' ) . '" id="uid-' . esc_attr( $user->id ) . '">' . __( 'Remove Invite', 'buddypress' ) . '</a>
1367 </div>';
1368
1369 if ( 'is_pending' == $user_status ) {
1370 echo '<p class="description">' . sprintf( __( '%s has previously requested to join this group. Sending an invitation will automatically add the member to the group.', 'buddypress' ), $user->user_link ) . '</p>';
1371 }
1372
1373 echo '</li>';
1374 exit;
1375
1376 } elseif ( 'uninvite' == $_POST['friend_action'] ) {
1377 // Users who have previously requested membership should not
1378 // have their requests deleted on the "uninvite" action.
1379 if ( BP_Groups_Member::check_for_membership_request( $friend_id, $group_id ) ) {
1380 return;
1381 }
1382
1383 // Remove the unsent invitation.
1384 if ( ! groups_uninvite_user( $friend_id, $group_id ) ) {
1385 return;
1386 }
1387
1388 exit;
1389
1390 } else {
1391 return;
1392 }
1393}
1394
1395/**
1396 * Friend/un-friend a user via a POST request.
1397 *
1398 * @since 1.2.0
1399 *
1400 * @return string|null HTML
1401 */
1402function bp_legacy_theme_ajax_addremove_friend() {
1403 if ( ! bp_is_post_request() ) {
1404 return;
1405 }
1406
1407 // Cast fid as an integer.
1408 $friend_id = (int) $_POST['fid'];
1409
1410 $user = get_user_by( 'id', $friend_id );
1411 if ( ! $user ) {
1412 die( __( 'No member found by that ID.', 'buddypress' ) );
1413 }
1414
1415 // Trying to cancel friendship.
1416 if ( 'is_friend' == BP_Friends_Friendship::check_is_friend( bp_loggedin_user_id(), $friend_id ) ) {
1417 check_ajax_referer( 'friends_remove_friend' );
1418
1419 if ( ! friends_remove_friend( bp_loggedin_user_id(), $friend_id ) ) {
1420 echo __( 'Friendship could not be canceled.', 'buddypress' );
1421 } else {
1422 echo '<a id="friend-' . esc_attr( $friend_id ) . '" class="friendship-button not_friends add" rel="add" href="' . wp_nonce_url( bp_loggedin_user_domain() . bp_get_friends_slug() . '/add-friend/' . $friend_id, 'friends_add_friend' ) . '">' . __( 'Add Friend', 'buddypress' ) . '</a>';
1423 }
1424
1425 // Trying to request friendship.
1426 } elseif ( 'not_friends' == BP_Friends_Friendship::check_is_friend( bp_loggedin_user_id(), $friend_id ) ) {
1427 check_ajax_referer( 'friends_add_friend' );
1428
1429 if ( ! friends_add_friend( bp_loggedin_user_id(), $friend_id ) ) {
1430 echo __(' Friendship could not be requested.', 'buddypress' );
1431 } else {
1432 echo '<a id="friend-' . esc_attr( $friend_id ) . '" class="remove friendship-button pending_friend requested" rel="remove" href="' . wp_nonce_url( bp_loggedin_user_domain() . bp_get_friends_slug() . '/requests/cancel/' . $friend_id . '/', 'friends_withdraw_friendship' ) . '" class="requested">' . __( 'Cancel Friendship Request', 'buddypress' ) . '</a>';
1433 }
1434
1435 // Trying to cancel pending request.
1436 } elseif ( 'pending' == BP_Friends_Friendship::check_is_friend( bp_loggedin_user_id(), $friend_id ) ) {
1437 check_ajax_referer( 'friends_withdraw_friendship' );
1438
1439 if ( friends_withdraw_friendship( bp_loggedin_user_id(), $friend_id ) ) {
1440 echo '<a id="friend-' . esc_attr( $friend_id ) . '" class="friendship-button not_friends add" rel="add" href="' . wp_nonce_url( bp_loggedin_user_domain() . bp_get_friends_slug() . '/add-friend/' . $friend_id, 'friends_add_friend' ) . '">' . __( 'Add Friend', 'buddypress' ) . '</a>';
1441 } else {
1442 echo __("Friendship request could not be cancelled.", 'buddypress');
1443 }
1444
1445 // Request already pending.
1446 } else {
1447 echo __( 'Request Pending', 'buddypress' );
1448 }
1449
1450 exit;
1451}
1452
1453/**
1454 * Accept a user friendship request via a POST request.
1455 *
1456 * @since 1.2.0
1457 *
1458 * @return mixed String on error, void on success.
1459 */
1460function bp_legacy_theme_ajax_accept_friendship() {
1461 if ( ! bp_is_post_request() ) {
1462 return;
1463 }
1464
1465 check_admin_referer( 'friends_accept_friendship' );
1466
1467 if ( ! friends_accept_friendship( (int) $_POST['id'] ) )
1468 echo "-1<div id='message' class='error'><p>" . __( 'There was a problem accepting that request. Please try again.', 'buddypress' ) . '</p></div>';
1469
1470 exit;
1471}
1472
1473/**
1474 * Reject a user friendship request via a POST request.
1475 *
1476 * @since 1.2.0
1477 *
1478 * @return mixed String on error, void on success.
1479 */
1480function bp_legacy_theme_ajax_reject_friendship() {
1481 if ( ! bp_is_post_request() ) {
1482 return;
1483 }
1484
1485 check_admin_referer( 'friends_reject_friendship' );
1486
1487 if ( ! friends_reject_friendship( (int) $_POST['id'] ) )
1488 echo "-1<div id='message' class='error'><p>" . __( 'There was a problem rejecting that request. Please try again.', 'buddypress' ) . '</p></div>';
1489
1490 exit;
1491}
1492
1493/**
1494 * Join or leave a group when clicking the "join/leave" button via a POST request.
1495 *
1496 * @since 1.2.0
1497 *
1498 * @return string|null HTML
1499 */
1500function bp_legacy_theme_ajax_joinleave_group() {
1501 if ( ! bp_is_post_request() ) {
1502 return;
1503 }
1504
1505 // Cast gid as integer.
1506 $group_id = (int) $_POST['gid'];
1507
1508 if ( groups_is_user_banned( bp_loggedin_user_id(), $group_id ) )
1509 return;
1510
1511 if ( ! $group = groups_get_group( $group_id ) )
1512 return;
1513
1514 if ( ! groups_is_user_member( bp_loggedin_user_id(), $group->id ) ) {
1515 if ( bp_current_user_can( 'groups_join_group', array( 'group_id' => $group->id ) ) ) {
1516 check_ajax_referer( 'groups_join_group' );
1517
1518 if ( ! groups_join_group( $group->id ) ) {
1519 _e( 'Error joining group', 'buddypress' );
1520 } else {
1521 echo '<a id="group-' . esc_attr( $group->id ) . '" class="group-button leave-group" rel="leave" href="' . wp_nonce_url( bp_get_group_permalink( $group ) . 'leave-group', 'groups_leave_group' ) . '">' . __( 'Leave Group', 'buddypress' ) . '</a>';
1522 }
1523
1524 } elseif ( bp_current_user_can( 'groups_request_membership', array( 'group_id' => $group->id ) ) ) {
1525
1526 // If the user has already been invited, then this is
1527 // an Accept Invitation button.
1528 if ( groups_check_user_has_invite( bp_loggedin_user_id(), $group->id ) ) {
1529 check_ajax_referer( 'groups_accept_invite' );
1530
1531 if ( ! groups_accept_invite( bp_loggedin_user_id(), $group->id ) ) {
1532 _e( 'Error requesting membership', 'buddypress' );
1533 } else {
1534 echo '<a id="group-' . esc_attr( $group->id ) . '" class="group-button leave-group" rel="leave" href="' . wp_nonce_url( bp_get_group_permalink( $group ) . 'leave-group', 'groups_leave_group' ) . '">' . __( 'Leave Group', 'buddypress' ) . '</a>';
1535 }
1536
1537 // Otherwise, it's a Request Membership button.
1538 } else {
1539 check_ajax_referer( 'groups_request_membership' );
1540
1541 if ( ! groups_send_membership_request( bp_loggedin_user_id(), $group->id ) ) {
1542 _e( 'Error requesting membership', 'buddypress' );
1543 } else {
1544 echo '<a id="group-' . esc_attr( $group->id ) . '" class="group-button disabled pending membership-requested" rel="membership-requested" href="' . bp_get_group_permalink( $group ) . '">' . __( 'Request Sent', 'buddypress' ) . '</a>';
1545 }
1546 }
1547 }
1548
1549 } else {
1550 check_ajax_referer( 'groups_leave_group' );
1551
1552 if ( ! groups_leave_group( $group->id ) ) {
1553 _e( 'Error leaving group', 'buddypress' );
1554 } elseif ( bp_current_user_can( 'groups_join_group', array( 'group_id' => $group->id ) ) ) {
1555 echo '<a id="group-' . esc_attr( $group->id ) . '" class="group-button join-group" rel="join" href="' . wp_nonce_url( bp_get_group_permalink( $group ) . 'join', 'groups_join_group' ) . '">' . __( 'Join Group', 'buddypress' ) . '</a>';
1556 } elseif ( bp_current_user_can( 'groups_request_membership', array( 'group_id' => $group->id ) ) ) {
1557 echo '<a id="group-' . esc_attr( $group->id ) . '" class="group-button request-membership" rel="join" href="' . wp_nonce_url( bp_get_group_permalink( $group ) . 'request-membership', 'groups_request_membership' ) . '">' . __( 'Request Membership', 'buddypress' ) . '</a>';
1558 }
1559 }
1560
1561 exit;
1562}
1563
1564/**
1565 * Close and keep closed site wide notices from an admin in the sidebar, via a POST request.
1566 *
1567 * @since 1.2.0
1568 *
1569 * @return mixed String on error, void on success.
1570 */
1571function bp_legacy_theme_ajax_close_notice() {
1572 if ( ! bp_is_post_request() ) {
1573 return;
1574 }
1575
1576 $nonce_check = isset( $_POST['nonce'] ) && wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'bp_messages_close_notice' );
1577
1578 if ( ! $nonce_check || ! isset( $_POST['notice_id'] ) ) {
1579 echo "-1<div id='message' class='error'><p>" . __( 'There was a problem closing the notice.', 'buddypress' ) . '</p></div>';
1580
1581 } else {
1582 $user_id = get_current_user_id();
1583 $notice_ids = bp_get_user_meta( $user_id, 'closed_notices', true );
1584 if ( ! is_array( $notice_ids ) ) {
1585 $notice_ids = array();
1586 }
1587
1588 $notice_ids[] = (int) $_POST['notice_id'];
1589
1590 bp_update_user_meta( $user_id, 'closed_notices', $notice_ids );
1591 }
1592
1593 exit;
1594}
1595
1596/**
1597 * Send a private message reply to a thread via a POST request.
1598 *
1599 * @since 1.2.0
1600 *
1601 * @return string|null HTML
1602 */
1603function bp_legacy_theme_ajax_messages_send_reply() {
1604 if ( ! bp_is_post_request() ) {
1605 return;
1606 }
1607
1608 check_ajax_referer( 'messages_send_message' );
1609
1610 $result = messages_new_message( array( 'thread_id' => (int) $_REQUEST['thread_id'], 'content' => $_REQUEST['content'] ) );
1611
1612 if ( !empty( $result ) ) {
1613
1614 // Pretend we're in the message loop.
1615 global $thread_template;
1616
1617 bp_thread_has_messages( array( 'thread_id' => (int) $_REQUEST['thread_id'] ) );
1618
1619 // Set the current message to the 2nd last.
1620 $thread_template->message = end( $thread_template->thread->messages );
1621 $thread_template->message = prev( $thread_template->thread->messages );
1622
1623 // Set current message to current key.
1624 $thread_template->current_message = key( $thread_template->thread->messages );
1625
1626 // Now manually iterate message like we're in the loop.
1627 bp_thread_the_message();
1628
1629 // Manually call oEmbed
1630 // this is needed because we're not at the beginning of the loop.
1631 bp_messages_embed();
1632
1633 // Add new-message css class.
1634 add_filter( 'bp_get_the_thread_message_css_class', function( $retval ) {
1635 $retval[] = 'new-message';
1636 return $retval;
1637 } );
1638
1639 // Output single message template part.
1640 bp_get_template_part( 'members/single/messages/message' );
1641
1642 // Clean up the loop.
1643 bp_thread_messages();
1644
1645 } else {
1646 echo "-1<div id='message' class='error'><p>" . __( 'There was a problem sending that reply. Please try again.', 'buddypress' ) . '</p></div>';
1647 }
1648
1649 exit;
1650}
1651
1652/**
1653 * Mark a private message as unread in your inbox via a POST request.
1654 *
1655 * No longer used.
1656 *
1657 * @since 1.2.0
1658 * @deprecated 2.2.0
1659 *
1660 * @return mixed String on error, void on success.
1661 */
1662function bp_legacy_theme_ajax_message_markunread() {
1663 die( '-1' );
1664}
1665
1666/**
1667 * Mark a private message as read in your inbox via a POST request.
1668 *
1669 * No longer used.
1670 *
1671 * @since 1.2.0
1672 * @deprecated 2.2.0
1673 *
1674 * @return mixed String on error, void on success.
1675 */
1676function bp_legacy_theme_ajax_message_markread() {
1677 die( '-1' );
1678}
1679
1680/**
1681 * Delete a private message(s) in your inbox via a POST request.
1682 *
1683 * No longer used.
1684 *
1685 * @since 1.2.0
1686 * @deprecated 2.2.0
1687 *
1688 * @return string|null HTML
1689 */
1690function bp_legacy_theme_ajax_messages_delete() {
1691 die( '-1' );
1692}
1693
1694/**
1695 * AJAX handler for autocomplete.
1696 *
1697 * Displays friends only, unless BP_MESSAGES_AUTOCOMPLETE_ALL is defined.
1698 *
1699 * @since 1.2.0
1700 */
1701function bp_legacy_theme_ajax_messages_autocomplete_results() {
1702
1703 /**
1704 * Filters the max results default value for ajax messages autocomplete results.
1705 *
1706 * @since 1.5.0
1707 *
1708 * @param int $value Max results for autocomplete. Default 10.
1709 */
1710 $limit = isset( $_GET['limit'] ) ? absint( $_GET['limit'] ) : (int) apply_filters( 'bp_autocomplete_max_results', 10 );
1711 $term = isset( $_GET['q'] ) ? sanitize_text_field( $_GET['q'] ) : '';
1712
1713 // Include everyone in the autocomplete, or just friends?
1714 if ( bp_is_current_component( bp_get_messages_slug() ) ) {
1715 $only_friends = ( buddypress()->messages->autocomplete_all === false );
1716 } else {
1717 $only_friends = true;
1718 }
1719
1720 $suggestions = bp_core_get_suggestions( array(
1721 'limit' => $limit,
1722 'only_friends' => $only_friends,
1723 'term' => $term,
1724 'type' => 'members',
1725 ) );
1726
1727 if ( $suggestions && ! is_wp_error( $suggestions ) ) {
1728 foreach ( $suggestions as $user ) {
1729
1730 // Note that the final line break acts as a delimiter for the
1731 // autocomplete JavaScript and thus should not be removed.
1732 printf( '<span id="%s" href="#"></span><img src="%s" style="width: 15px"> %s (%s)' . "\n",
1733 esc_attr( 'link-' . $user->ID ),
1734 esc_url( $user->image ),
1735 esc_html( $user->name ),
1736 esc_html( $user->ID )
1737 );
1738 }
1739 }
1740
1741 exit;
1742}
1743
1744/**
1745 * AJAX callback to set a message's star status.
1746 *
1747 * @since 2.3.0
1748 */
1749function bp_legacy_theme_ajax_messages_star_handler() {
1750 if ( false === bp_is_active( 'messages', 'star' ) || empty( $_POST['message_id'] ) ) {
1751 return;
1752 }
1753
1754 // Check nonce.
1755 check_ajax_referer( 'bp-messages-star-' . (int) $_POST['message_id'], 'nonce' );
1756
1757 // Check capability.
1758 if ( ! is_user_logged_in() || ! bp_core_can_edit_settings() ) {
1759 return;
1760 }
1761
1762 if ( true === bp_messages_star_set_action( array(
1763 'action' => $_POST['star_status'],
1764 'message_id' => (int) $_POST['message_id'],
1765 'bulk' => ! empty( $_POST['bulk'] ) ? true : false
1766 ) ) ) {
1767 echo '1';
1768 die();
1769 }
1770
1771 echo '-1';
1772 die();
1773}
1774
1775/**
1776 * BP Legacy's callback for the cover image feature.
1777 *
1778 * @since 2.4.0
1779 *
1780 * @param array $params the current component's feature parameters.
1781 * @return null|string An array to inform about the css handle to attach the css rules to
1782 */
1783function bp_legacy_theme_cover_image( $params = array() ) {
1784 if ( empty( $params ) ) {
1785 return;
1786 }
1787
1788 // Avatar height - padding - 1/2 avatar height.
1789 $avatar_offset = $params['height'] - 5 - round( (int) bp_core_avatar_full_height() / 2 );
1790
1791 // Header content offset + spacing.
1792 $top_offset = bp_core_avatar_full_height() - 10;
1793 $left_offset = bp_core_avatar_full_width() + 20;
1794
1795 $cover_image = ( !empty( $params['cover_image'] ) ) ? 'background-image: url(' . $params['cover_image'] . ');' : '';
1796
1797 $hide_avatar_style = '';
1798
1799 // Adjust the cover image header, in case avatars are completely disabled.
1800 if ( ! buddypress()->avatar->show_avatars ) {
1801 $hide_avatar_style = '
1802 #buddypress #item-header-cover-image #item-header-avatar {
1803 display: none;
1804 }
1805 ';
1806
1807 if ( bp_is_user() ) {
1808 $hide_avatar_style = '
1809 #buddypress #item-header-cover-image #item-header-avatar a {
1810 display: block;
1811 height: ' . $top_offset . 'px;
1812 margin: 0 15px 19px 0;
1813 }
1814
1815 #buddypress div#item-header #item-header-cover-image #item-header-content {
1816 margin-left: auto;
1817 }
1818 ';
1819 }
1820 }
1821
1822 return '
1823 /* Cover image */
1824 #buddypress #header-cover-image {
1825 height: ' . $params["height"] . 'px;
1826 ' . $cover_image . '
1827 }
1828
1829 #buddypress #create-group-form #header-cover-image {
1830 margin: 1em 0;
1831 position: relative;
1832 }
1833
1834 .bp-user #buddypress #item-header {
1835 padding-top: 0;
1836 }
1837
1838 #buddypress #item-header-cover-image #item-header-avatar {
1839 margin-top: '. $avatar_offset .'px;
1840 float: left;
1841 overflow: visible;
1842 width: auto;
1843 }
1844
1845 #buddypress div#item-header #item-header-cover-image #item-header-content {
1846 clear: both;
1847 float: left;
1848 margin-left: ' . $left_offset . 'px;
1849 margin-top: -' . $top_offset . 'px;
1850 width: auto;
1851 }
1852
1853 body.single-item.groups #buddypress div#item-header #item-header-cover-image #item-header-content,
1854 body.single-item.groups #buddypress div#item-header #item-header-cover-image #item-actions {
1855 clear: none;
1856 margin-top: ' . $params["height"] . 'px;
1857 margin-left: 0;
1858 max-width: 50%;
1859 }
1860
1861 body.single-item.groups #buddypress div#item-header #item-header-cover-image #item-actions {
1862 max-width: 20%;
1863 padding-top: 20px;
1864 }
1865
1866 ' . $hide_avatar_style . '
1867
1868 #buddypress div#item-header-cover-image .user-nicename a,
1869 #buddypress div#item-header-cover-image .user-nicename {
1870 font-size: 200%;
1871 color: #fff;
1872 margin: 0 0 0.6em;
1873 text-rendering: optimizelegibility;
1874 text-shadow: 0 0 3px rgba( 0, 0, 0, 0.8 );
1875 }
1876
1877 #buddypress #item-header-cover-image #item-header-avatar img.avatar {
1878 background: rgba( 255, 255, 255, 0.8 );
1879 border: solid 2px #fff;
1880 }
1881
1882 #buddypress #item-header-cover-image #item-header-avatar a {
1883 border: 0;
1884 text-decoration: none;
1885 }
1886
1887 #buddypress #item-header-cover-image #item-buttons {
1888 margin: 0 0 10px;
1889 padding: 0 0 5px;
1890 }
1891
1892 #buddypress #item-header-cover-image #item-buttons:after {
1893 clear: both;
1894 content: "";
1895 display: table;
1896 }
1897
1898 @media screen and (max-width: 782px) {
1899 #buddypress #item-header-cover-image #item-header-avatar,
1900 .bp-user #buddypress #item-header #item-header-cover-image #item-header-avatar,
1901 #buddypress div#item-header #item-header-cover-image #item-header-content {
1902 width: 100%;
1903 text-align: center;
1904 }
1905
1906 #buddypress #item-header-cover-image #item-header-avatar a {
1907 display: inline-block;
1908 }
1909
1910 #buddypress #item-header-cover-image #item-header-avatar img {
1911 margin: 0;
1912 }
1913
1914 #buddypress div#item-header #item-header-cover-image #item-header-content,
1915 body.single-item.groups #buddypress div#item-header #item-header-cover-image #item-header-content,
1916 body.single-item.groups #buddypress div#item-header #item-header-cover-image #item-actions {
1917 margin: 0;
1918 }
1919
1920 body.single-item.groups #buddypress div#item-header #item-header-cover-image #item-header-content,
1921 body.single-item.groups #buddypress div#item-header #item-header-cover-image #item-actions {
1922 max-width: 100%;
1923 }
1924
1925 #buddypress div#item-header-cover-image h2 a,
1926 #buddypress div#item-header-cover-image h2 {
1927 color: inherit;
1928 text-shadow: none;
1929 margin: 25px 0 0;
1930 font-size: 200%;
1931 }
1932
1933 #buddypress #item-header-cover-image #item-buttons div {
1934 float: none;
1935 display: inline-block;
1936 }
1937
1938 #buddypress #item-header-cover-image #item-buttons:before {
1939 content: "";
1940 }
1941
1942 #buddypress #item-header-cover-image #item-buttons {
1943 margin: 5px 0;
1944 }
1945 }
1946 ';
1947}
1948
1949/**
1950 * Add a search box to a single group's manage members screen.
1951 *
1952 * @since 2.7.0
1953 *
1954 * @return string|null HTML for the search form.
1955 */
1956function bp_legacy_theme_group_manage_members_add_search() {
1957 if ( bp_is_action_variable( 'manage-members' ) ) :
1958 ?>
1959 <div id="members-dir-search" class="dir-search no-ajax" role="search">
1960 <?php bp_directory_members_search_form(); ?>
1961 </div>
1962 <?php
1963 endif;
1964}