[ Index ]

PHP Cross Reference of BuddyPress

title

Body

[close]

/src/bp-templates/bp-nouveau/ -> buddypress-functions.php (source)

   1  <?php
   2  /**
   3   * Functions of BuddyPress's "Nouveau" template pack.
   4   *
   5   * @since 3.0.0
   6   * @version 3.1.0
   7   *
   8   * @buddypress-template-pack {
   9   *   Template Pack ID:       nouveau
  10   *   Template Pack Name:     BP Nouveau
  11   *   Version:                1.0.0
  12   *   WP required version:    4.5.0
  13   *   BP required version:    3.0.0
  14   *   Description:            A new template pack for BuddyPress!
  15   *   Text Domain:            bp-nouveau
  16   *   Domain Path:            /languages/
  17   *   Author:                 The BuddyPress community
  18   *   Template Pack Supports: activity, blogs, friends, groups, messages, notifications, settings, xprofile
  19   * }}
  20   */
  21  
  22  // Exit if accessed directly.
  23  defined( 'ABSPATH' ) || exit;
  24  
  25  /** Theme Setup ***************************************************************/
  26  
  27  /**
  28   * Loads BuddyPress Nouveau Template pack functionality.
  29   *
  30   * See @link BP_Theme_Compat() for more.
  31   *
  32   * @since 3.0.0
  33   */
  34  class BP_Nouveau extends BP_Theme_Compat {
  35      /**
  36       * Instance of this class.
  37       */
  38      protected static $instance = null;
  39  
  40      /**
  41       * Return the instance of this class.
  42       *
  43       * @since 3.0.0
  44       */
  45  	public static function get_instance() {
  46          if ( null === self::$instance ) {
  47              self::$instance = new self;
  48          }
  49  
  50          return self::$instance;
  51      }
  52  
  53      /**
  54       * The BP Nouveau constructor.
  55       *
  56       * @since 3.0.0
  57       */
  58  	public function __construct() {
  59          parent::start();
  60  
  61          $this->includes();
  62          $this->setup_support();
  63      }
  64  
  65      /**
  66       * BP Nouveau global variables.
  67       *
  68       * @since 3.0.0
  69       */
  70  	protected function setup_globals() {
  71          $bp = buddypress();
  72  
  73          foreach ( $bp->theme_compat->packages['nouveau'] as $property => $value ) {
  74              $this->{$property} = $value;
  75          }
  76  
  77          $this->includes_dir  = trailingslashit( $this->dir ) . 'includes/';
  78          $this->directory_nav = new BP_Core_Nav();
  79      }
  80  
  81      /**
  82       * Includes!
  83       *
  84       * @since 3.0.0
  85       */
  86  	protected function includes() {
  87          require $this->includes_dir . 'functions.php';
  88          require $this->includes_dir . 'classes.php';
  89          require $this->includes_dir . 'template-tags.php';
  90  
  91          // Test suite requires the AJAX functions early.
  92          if ( function_exists( 'tests_add_filter' ) ) {
  93              require $this->includes_dir . 'ajax.php';
  94  
  95          // Load AJAX code only on AJAX requests.
  96          } else {
  97              add_action( 'admin_init', function() {
  98                  if ( defined( 'DOING_AJAX' ) && true === DOING_AJAX ) {
  99                      require bp_nouveau()->includes_dir . 'ajax.php';
 100                  }
 101              }, 0 );
 102          }
 103  
 104          add_action( 'bp_customize_register', function() {
 105              if ( bp_is_root_blog() && current_user_can( 'customize' ) ) {
 106                  require bp_nouveau()->includes_dir . 'customizer.php';
 107              }
 108          }, 0 );
 109  
 110          foreach ( bp_core_get_packaged_component_ids() as $component ) {
 111              $component_loader = trailingslashit( $this->includes_dir ) . $component . '/loader.php';
 112  
 113              if ( ! bp_is_active( $component ) || ! file_exists( $component_loader ) ) {
 114                  continue;
 115              }
 116  
 117              require( $component_loader );
 118          }
 119  
 120          /**
 121           * Fires after all of the BuddyPress Nouveau includes have been loaded. Passed by reference.
 122           *
 123           * @since 3.0.0
 124           *
 125           * @param BP_Nouveau $value Current BP_Nouveau instance.
 126           */
 127          do_action_ref_array( 'bp_nouveau_includes', array( &$this ) );
 128      }
 129  
 130      /**
 131       * Setup the Template Pack features support.
 132       *
 133       * @since 3.0.0
 134       */
 135  	protected function setup_support() {
 136          $width         = 1300;
 137          $top_offset    = 150;
 138  
 139          /** This filter is documented in bp-core/bp-core-avatars.php. */
 140          $avatar_height = apply_filters( 'bp_core_avatar_full_height', $top_offset );
 141  
 142          if ( $avatar_height > $top_offset ) {
 143              $top_offset = $avatar_height;
 144          }
 145  
 146          bp_set_theme_compat_feature( $this->id, array(
 147              'name'     => 'cover_image',
 148              'settings' => array(
 149                  'components'   => array( 'members', 'groups' ),
 150                  'width'        => $width,
 151                  'height'       => $top_offset + round( $avatar_height / 2 ),
 152                  'callback'     => 'bp_nouveau_theme_cover_image',
 153                  'theme_handle' => 'bp-nouveau',
 154              ),
 155          ) );
 156      }
 157  
 158      /**
 159       * Setup the Template Pack common actions.
 160       *
 161       * @since 3.0.0
 162       */
 163  	protected function setup_actions() {
 164          // Filter BuddyPress template hierarchy and look for page templates.
 165          add_filter( 'bp_get_buddypress_template', array( $this, 'theme_compat_page_templates' ), 10, 1 );
 166  
 167          // Add our "buddypress" div wrapper to theme compat template parts.
 168          add_filter( 'bp_replace_the_content', array( $this, 'theme_compat_wrapper' ), 999 );
 169  
 170          // We need to neutralize the BuddyPress core "bp_core_render_message()" once it has been added.
 171          add_action( 'bp_actions', array( $this, 'neutralize_core_template_notices' ), 6 );
 172  
 173          // Scripts.
 174          add_action( 'bp_enqueue_scripts', array( $this, 'register_scripts' ), 2 ); // Register theme JS.
 175          remove_action( 'bp_enqueue_scripts', 'bp_core_confirmation_js' );
 176          add_action( 'bp_enqueue_scripts', array( $this, 'enqueue_styles' ) ); // Enqueue theme CSS.
 177          add_action( 'bp_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); // Enqueue theme JS.
 178          add_filter( 'bp_enqueue_scripts', array( $this, 'localize_scripts' ) ); // Enqueue theme script localization.
 179  
 180          // Body no-js class.
 181          add_filter( 'body_class', array( $this, 'add_nojs_body_class' ), 20, 1 );
 182  
 183          // Ajax querystring.
 184          add_filter( 'bp_ajax_querystring', 'bp_nouveau_ajax_querystring', 10, 2 );
 185  
 186          // Register directory nav items.
 187          add_action( 'bp_screens', array( $this, 'setup_directory_nav' ), 15 );
 188  
 189          // Register the Default front pages Dynamic Sidebars.
 190          add_action( 'widgets_init', 'bp_nouveau_register_sidebars', 11 );
 191  
 192          // Register the Primary Object nav widget.
 193          add_action( 'bp_widgets_init', array( 'BP_Nouveau_Object_Nav_Widget', 'register_widget' ) );
 194  
 195          // Set the BP Uri for the Ajax customizer preview.
 196          add_filter( 'bp_uri', array( $this, 'customizer_set_uri' ), 10, 1 );
 197  
 198          /** Override **********************************************************/
 199  
 200          /**
 201           * Fires after all of the BuddyPress theme compat actions have been added.
 202           *
 203           * @since 3.0.0
 204           *
 205           * @param BP_Nouveau $this Current BP_Nouveau instance.
 206           */
 207          do_action_ref_array( 'bp_theme_compat_actions', array( &$this ) );
 208      }
 209  
 210      /**
 211       * Enqueue the template pack css files
 212       *
 213       * @since 3.0.0
 214       */
 215  	public function enqueue_styles() {
 216          $min = bp_core_get_minified_asset_suffix();
 217          $rtl = '';
 218  
 219          if ( is_rtl() ) {
 220              $rtl = '-rtl';
 221          }
 222  
 223          /**
 224           * Filters the BuddyPress Nouveau CSS dependencies.
 225           *
 226           * @since 3.0.0
 227           *
 228           * @param array $value Array of style dependencies. Default Dashicons.
 229           */
 230          $css_dependencies = apply_filters( 'bp_nouveau_css_dependencies', array( 'dashicons' ) );
 231  
 232          /**
 233           * Filters the styles to enqueue for BuddyPress Nouveau.
 234           *
 235           * This filter provides a multidimensional array that will map to arguments used for wp_enqueue_style().
 236           * The primary index should have the stylesheet handle to use, and be assigned an array that has indexes for
 237           * file location, dependencies, and version.
 238           *
 239           * @since 3.0.0
 240           *
 241           * @param array $value Array of styles to enqueue.
 242           */
 243          $styles = apply_filters( 'bp_nouveau_enqueue_styles', array(
 244              'bp-nouveau' => array(
 245                  'file' => 'css/buddypress%1$s%2$s.css', 'dependencies' => $css_dependencies, 'version' => $this->version,
 246              ),
 247          ) );
 248  
 249          if ( $styles ) {
 250  
 251              foreach ( $styles as $handle => $style ) {
 252                  if ( ! isset( $style['file'] ) ) {
 253                      continue;
 254                  }
 255  
 256                  $file = sprintf( $style['file'], $rtl, $min );
 257  
 258                  // Locate the asset if needed.
 259                  if ( false === strpos( $style['file'], '://' ) ) {
 260                      $asset = bp_locate_template_asset( $file );
 261  
 262                      if ( empty( $asset['uri'] ) || false === strpos( $asset['uri'], '://' ) ) {
 263                          continue;
 264                      }
 265  
 266                      $file = $asset['uri'];
 267                  }
 268  
 269                  $data = bp_parse_args(
 270                      $style,
 271                      array(
 272                          'dependencies' => array(),
 273                          'version'      => $this->version,
 274                          'type'         => 'screen',
 275                      ),
 276                      'nouveau_enqueue_styles'
 277                  );
 278  
 279                  wp_enqueue_style( $handle, $file, $data['dependencies'], $data['version'], $data['type'] );
 280  
 281                  if ( $min ) {
 282                      wp_style_add_data( $handle, 'suffix', $min );
 283                  }
 284              }
 285          }
 286  
 287          // Compatibility stylesheets for specific themes.
 288          $theme                = get_template();
 289          $companion_stylesheet = bp_locate_template_asset( sprintf( 'css/%1$s%2$s.css', $theme, $min ) );
 290          $companion_handle     = 'bp-' . $theme;
 291  
 292          if ( ! is_rtl() && isset( $companion_stylesheet['uri'] ) && $companion_stylesheet['uri'] ) {
 293              wp_enqueue_style( $companion_handle, $companion_stylesheet['uri'], array(), $this->version, 'screen' );
 294  
 295              if ( $min ) {
 296                  wp_style_add_data( $companion_handle, 'suffix', $min );
 297              }
 298          }
 299  
 300          // Compatibility stylesheet for specific themes, RTL-version.
 301          if ( is_rtl() ) {
 302              $rtl_companion_stylesheet = bp_locate_template_asset( sprintf( 'css/%1$s-rtl%2$s.css', $theme, $min ) );
 303  
 304              if ( isset( $rtl_companion_stylesheet['uri'] ) ) {
 305                  $companion_handle .= '-rtl';
 306                  wp_enqueue_style( $companion_handle, $rtl_companion_stylesheet['uri'], array(), $this->version, 'screen' );
 307  
 308                  if ( $min ) {
 309                      wp_style_add_data( $companion_handle, 'suffix', $min );
 310                  }
 311              }
 312          }
 313      }
 314  
 315      /**
 316       * Register Template Pack JavaScript files
 317       *
 318       * @since 3.0.0
 319       */
 320  	public function register_scripts() {
 321          $min          = bp_core_get_minified_asset_suffix();
 322          $dependencies = bp_core_get_js_dependencies();
 323          $bp_confirm   = array_search( 'bp-confirm', $dependencies );
 324  
 325          unset( $dependencies[ $bp_confirm ] );
 326  
 327          /**
 328           * Filters the scripts to enqueue for BuddyPress Nouveau.
 329           *
 330           * This filter provides a multidimensional array that will map to arguments used for wp_register_script().
 331           * The primary index should have the script handle to use, and be assigned an array that has indexes for
 332           * file location, dependencies, version and if it should load in the footer or not.
 333           *
 334           * @since 3.0.0
 335           *
 336           * @param array $value Array of scripts to register.
 337           */
 338          $scripts = apply_filters( 'bp_nouveau_register_scripts', array(
 339              'bp-nouveau' => array(
 340                  'file'         => 'js/buddypress-nouveau%s.js',
 341                  'dependencies' => $dependencies,
 342                  'version'      => $this->version,
 343                  'footer'       => true,
 344              ),
 345          ) );
 346  
 347          // Bail if no scripts.
 348          if ( empty( $scripts ) ) {
 349              return;
 350          }
 351  
 352          // Add The password verify if needed.
 353          if ( bp_is_active( 'settings' ) || bp_get_signup_allowed() ) {
 354              /**
 355               * BP Nouveau is now directly using the `wp-admin/js/user-profile.js` script.
 356               *
 357               * Setting the user password is now more consistent with how WordPress handles it.
 358               *
 359               * @deprecated 5.0.0
 360               */
 361              $scripts['bp-nouveau-password-verify'] = array(
 362                  'file'         => 'js/password-verify%s.js',
 363                  'dependencies' => array( 'bp-nouveau', 'password-strength-meter' ),
 364                  'footer'       => true,
 365              );
 366          }
 367  
 368          foreach ( $scripts as $handle => $script ) {
 369              if ( ! isset( $script['file'] ) ) {
 370                  continue;
 371              }
 372  
 373              $file = sprintf( $script['file'], $min );
 374  
 375              // Locate the asset if needed.
 376              if ( false === strpos( $script['file'], '://' ) ) {
 377                  $asset = bp_locate_template_asset( $file );
 378  
 379                  if ( empty( $asset['uri'] ) || false === strpos( $asset['uri'], '://' ) ) {
 380                      continue;
 381                  }
 382  
 383                  $file = $asset['uri'];
 384              }
 385  
 386              $data = bp_parse_args(
 387                  $script,
 388                  array(
 389                      'dependencies' => array(),
 390                      'version'      => $this->version,
 391                      'footer'       => false,
 392                  ),
 393                  'nouveau_register_scripts'
 394              );
 395  
 396              wp_register_script( $handle, $file, $data['dependencies'], $data['version'], $data['footer'] );
 397          }
 398      }
 399  
 400      /**
 401       * Enqueue the required JavaScript files
 402       *
 403       * @since 3.0.0
 404       */
 405  	public function enqueue_scripts() {
 406          wp_enqueue_script( 'bp-nouveau' );
 407  
 408          if ( bp_is_register_page() || bp_is_user_settings_general() ) {
 409              wp_enqueue_script( 'user-profile' );
 410          }
 411  
 412          if ( is_singular() && bp_is_blog_page() && get_option( 'thread_comments' ) ) {
 413              wp_enqueue_script( 'comment-reply' );
 414          }
 415  
 416          /**
 417           * Fires after all of the BuddyPress Nouveau scripts have been enqueued.
 418           *
 419           * @since 3.0.0
 420           */
 421          do_action( 'bp_nouveau_enqueue_scripts' );
 422      }
 423  
 424      /**
 425       * Adds the no-js class to the body tag.
 426       *
 427       * This function ensures that the <body> element will have the 'no-js' class by default. If you're
 428       * using JavaScript for some visual functionality in your theme, and you want to provide noscript
 429       * support, apply those styles to body.no-js.
 430       *
 431       * The no-js class is removed by the JavaScript created in buddypress.js.
 432       *
 433       * @since 3.0.0
 434       *
 435       * @param array $classes Array of classes to append to body tag.
 436       *
 437       * @return array $classes
 438       */
 439  	public function add_nojs_body_class( $classes ) {
 440          $classes[] = 'no-js';
 441          return array_unique( $classes );
 442      }
 443  
 444      /**
 445       * Load localizations for topic script.
 446       *
 447       * These localizations require information that may not be loaded even by init.
 448       *
 449       * @since 3.0.0
 450       */
 451  	public function localize_scripts() {
 452          $params = array(
 453              'ajaxurl'             => bp_core_ajax_url(),
 454              'confirm'             => __( 'Are you sure?', 'buddypress' ),
 455  
 456              /* translators: %s: number of activity comments */
 457              'show_x_comments'     => __( 'Show all %d comments', 'buddypress' ),
 458              'unsaved_changes'     => __( 'Your profile has unsaved changes. If you leave the page, the changes will be lost.', 'buddypress' ),
 459              'object_nav_parent'   => '#buddypress',
 460          );
 461  
 462          // If the Object/Item nav are in the sidebar.
 463          if ( bp_nouveau_is_object_nav_in_sidebar() ) {
 464              $params['object_nav_parent'] = '.buddypress_object_nav';
 465          }
 466  
 467          /**
 468           * Filters the supported BuddyPress Nouveau components.
 469           *
 470           * @since 3.0.0
 471           *
 472           * @param array $value Array of supported components.
 473           */
 474          $supported_objects = (array) apply_filters( 'bp_nouveau_supported_components', bp_core_get_packaged_component_ids() );
 475          $object_nonces     = array();
 476  
 477          foreach ( $supported_objects as $key_object => $object ) {
 478              if ( ! bp_is_active( $object ) || 'forums' === $object ) {
 479                  unset( $supported_objects[ $key_object ] );
 480                  continue;
 481              }
 482  
 483              $object_nonces[ $object ] = wp_create_nonce( 'bp_nouveau_' . $object );
 484          }
 485  
 486          // Groups require some additional objects.
 487          if ( bp_is_active( 'groups' ) ) {
 488              $supported_objects = array_merge( $supported_objects, array( 'group_members', 'group_requests' ) );
 489          }
 490  
 491          // Add components & nonces.
 492          $params['objects'] = $supported_objects;
 493          $params['nonces']  = $object_nonces;
 494  
 495          // Used to transport the settings inside the Ajax requests.
 496          if ( is_customize_preview() ) {
 497              $params['customizer_settings'] = bp_nouveau_get_temporary_setting( 'any' );
 498          }
 499  
 500          /**
 501           * Filters core JavaScript strings for internationalization before AJAX usage.
 502           *
 503           * @since 3.0.0
 504           *
 505           * @param array $params Array of key/value pairs for AJAX usage.
 506           */
 507          wp_localize_script( 'bp-nouveau', 'BP_Nouveau', apply_filters( 'bp_core_get_js_strings', $params ) );
 508      }
 509  
 510      /**
 511       * Filter the default theme compatibility root template hierarchy, and prepend
 512       * a page template to the front if it's set.
 513       *
 514       * @see https://buddypress.trac.wordpress.org/ticket/6065
 515       *
 516       * @since 3.0.0
 517       *
 518       * @param array $templates Array of templates.
 519       *
 520       * @return array
 521       */
 522  	public function theme_compat_page_templates( $templates = array() ) {
 523          /**
 524           * Filters whether or not we are looking at a directory to determine if to return early.
 525           *
 526           * @since 3.0.0
 527           *
 528           * @param bool $value Whether or not we are viewing a directory.
 529           */
 530          if ( true === (bool) apply_filters( 'bp_nouveau_theme_compat_page_templates_directory_only', ! bp_is_directory() ) ) {
 531              return $templates;
 532          }
 533  
 534          // No page ID yet.
 535          $page_id = 0;
 536  
 537          // Get the WordPress Page ID for the current view.
 538          foreach ( (array) buddypress()->pages as $component => $bp_page ) {
 539  
 540              // Handles the majority of components.
 541              if ( bp_is_current_component( $component ) ) {
 542                  $page_id = (int) $bp_page->id;
 543              }
 544  
 545              // Stop if not on a user page.
 546              if ( ! bp_is_user() && ! empty( $page_id ) ) {
 547                  break;
 548              }
 549  
 550              // The Members component requires an explicit check due to overlapping components.
 551              if ( bp_is_user() && ( 'members' === $component ) ) {
 552                  $page_id = (int) $bp_page->id;
 553                  break;
 554              }
 555          }
 556  
 557          // Bail if no directory page set.
 558          if ( 0 === $page_id ) {
 559              return $templates;
 560          }
 561  
 562          // Check for page template.
 563          $page_template = get_page_template_slug( $page_id );
 564  
 565          // Add it to the beginning of the templates array so it takes precedence over the default hierarchy.
 566          if ( ! empty( $page_template ) ) {
 567  
 568              /**
 569               * Check for existence of template before adding it to template
 570               * stack to avoid accidentally including an unintended file.
 571               *
 572               * @see https://buddypress.trac.wordpress.org/ticket/6190
 573               */
 574              if ( '' !== locate_template( $page_template ) ) {
 575                  array_unshift( $templates, $page_template );
 576              }
 577          }
 578  
 579          return $templates;
 580      }
 581  
 582      /**
 583       * Add our special 'buddypress' div wrapper to the theme compat template part.
 584       *
 585       * @since 3.0.0
 586       *
 587       * @see bp_buffer_template_part()
 588       *
 589       * @param string $retval Current template part contents.
 590       *
 591       * @return string
 592       */
 593  	public function theme_compat_wrapper( $retval ) {
 594          if ( false !== strpos( $retval, '<div id="buddypress"' ) ) {
 595              return $retval;
 596          }
 597  
 598          // Add our 'buddypress' div wrapper.
 599          return sprintf(
 600              '<div id="buddypress" class="%1$s">%2$s</div><!-- #buddypress -->%3$s',
 601              esc_attr( bp_nouveau_get_container_classes() ),
 602              $retval,  // Constructed HTML.
 603              "\n"
 604          );
 605      }
 606  
 607      /**
 608       * Define the directory nav items
 609       *
 610       * @since 3.0.0
 611       */
 612  	public function setup_directory_nav() {
 613          $nav_items = array();
 614  
 615          if ( bp_is_members_directory() ) {
 616              $nav_items = bp_nouveau_get_members_directory_nav_items();
 617          } elseif ( bp_is_activity_directory() ) {
 618              $nav_items = bp_nouveau_get_activity_directory_nav_items();
 619          } elseif ( bp_is_groups_directory() ) {
 620              $nav_items = bp_nouveau_get_groups_directory_nav_items();
 621          } elseif ( bp_is_blogs_directory() ) {
 622              $nav_items = bp_nouveau_get_blogs_directory_nav_items();
 623          }
 624  
 625          if ( empty( $nav_items ) ) {
 626              return;
 627          }
 628  
 629          foreach ( $nav_items as $nav_item ) {
 630              if ( empty( $nav_item['component'] ) || $nav_item['component'] !== bp_current_component() ) {
 631                  continue;
 632              }
 633  
 634              // Define the primary nav for the current component's directory.
 635              $this->directory_nav->add_nav( $nav_item );
 636          }
 637      }
 638  
 639      /**
 640       * We'll handle template notices from BP Nouveau.
 641       *
 642       * @since 3.0.0
 643       */
 644  	public function neutralize_core_template_notices() {
 645          remove_action( 'template_notices', 'bp_core_render_message' );
 646      }
 647  
 648      /**
 649       * Set the BP Uri for the customizer in case of Ajax requests.
 650       *
 651       * @since 3.0.0
 652       *
 653       * @param  string $path the BP Uri.
 654       * @return string       the BP Uri.
 655       */
 656  	public function customizer_set_uri( $path ) {
 657          if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) {
 658              return $path;
 659          }
 660  
 661          $uri = parse_url( $path );
 662  
 663          if ( false === strpos( $uri['path'], 'customize.php' ) ) {
 664              return $path;
 665          } else {
 666              $vars = bp_parse_args(
 667                  $uri['query'],
 668                  array(),
 669                  'customizer_set_uri'
 670              );
 671  
 672              if ( ! empty( $vars['url'] ) ) {
 673                  $path = str_replace( get_site_url(), '', urldecode( $vars['url'] ) );
 674              }
 675          }
 676  
 677          return $path;
 678      }
 679  }
 680  
 681  /**
 682   * Get a unique instance of BP Nouveau
 683   *
 684   * @since 3.0.0
 685   *
 686   * @return BP_Nouveau the main instance of the class
 687   */
 688  function bp_nouveau() {
 689      return BP_Nouveau::get_instance();
 690  }
 691  
 692  /**
 693   * Launch BP Nouveau!
 694   */
 695  bp_nouveau();


Generated: Sat Dec 5 01:01:41 2020 Cross-referenced by PHPXref 0.7.1