PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.1
16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 All 504 releases
← All changes | sal/class.json-api-site-base.php +397 -54 12.7.316.3-a.1 View file →
@@ -14,8 +14,12 @@
14 14 use Automattic\Jetpack\Blaze;
15 15 use Automattic\Jetpack\Status;
16 16 use Automattic\Jetpack\Status\Host;
17 17
18 +if ( ! defined( 'ABSPATH' ) ) {
19 + exit( 0 );
20 +}
21 +
18 22 require_once __DIR__ . '/class.json-api-date.php';
19 23 require_once __DIR__ . '/class.json-api-post-base.php';
20 24
21 25 /**
@@ -60,14 +64,24 @@
60 64 return $this->blog_id;
61 65 }
62 66
63 67 /**
68 + * Returns the site slug.
69 + *
70 + * @return string
71 + */
72 + public function get_slug() {
73 + return ( new Status() )->get_site_suffix();
74 + }
75 +
76 + /**
64 77 * Returns the site name.
65 78 *
66 79 * @return string
67 80 */
68 81 public function get_name() {
69 - return (string) htmlspecialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
82 + $name = get_bloginfo( 'name' );
83 + return is_string( $name ) ? htmlspecialchars_decode( $name, ENT_QUOTES ) : '';
70 84 }
71 85
72 86 /**
73 87 * Returns the site description.
@@ -74,9 +88,10 @@
74 88 *
75 89 * @return string
76 90 */
77 91 public function get_description() {
78 - return (string) htmlspecialchars_decode( get_bloginfo( 'description' ), ENT_QUOTES );
92 + $description = get_bloginfo( 'description' );
93 + return is_string( $description ) ? htmlspecialchars_decode( $description, ENT_QUOTES ) : '';
79 94 }
80 95
81 96 /**
82 97 * Returns the URL for the current site.
@@ -409,8 +424,45 @@
409 424 */
410 425 abstract public function get_user_interactions();
411 426
412 427 /**
428 + * Flag a site as deleted. Not used in Jetpack.
429 + *
430 + * @see class.json-api-site-jetpack.php for implementation.
431 + */
432 + abstract public function is_deleted();
433 +
434 + /**
435 + * Indicates that a site is an A4A client. Not used in Jetpack.
436 + *
437 + * @see class.json-api-site-jetpack.php for implementation.
438 + */
439 + abstract public function is_a4a_client();
440 +
441 + /**
442 + * Indicates that a site is an A4A dev site.
443 + *
444 + * @return bool
445 + */
446 + public function is_a4a_dev_site() {
447 + if ( function_exists( 'has_blog_sticker' ) ) {
448 + return has_blog_sticker( 'a4a-is-dev-site' );
449 + }
450 + return false;
451 + }
452 +
453 + /**
454 + * Return the user interactions with a site. Not used in Jetpack.
455 + *
456 + * @param string $role The capability to check.
457 + * @return bool
458 + * @see class.json-api-site-jetpack.php for implementation.
459 + * @see class.json-api-site-wpcom.php (on WPCOM) for Simple-site implementation.
460 + * @see class.json-api-site-jetpack-shadow.php (on WPCOM) for Atomic-site implementation.
461 + */
462 + abstract public function current_user_can( $role );
463 +
464 + /**
413 465 * Defines a filter to set whether a site is an automated_transfer site or not.
414 466 *
415 467 * Default is false.
416 468 *
@@ -438,9 +490,9 @@
438 490 * Defaulting to false and not relevant for Jetpack sites, this is expanded on the WordPress.com side for a specific wp.com/start 'WP for teams' flow.
439 491 *
440 492 * @see class.json-api-site-jetpack.php for implementation.
441 493 */
442 - abstract protected function is_wpforteams_site();
494 + abstract public function is_wpforteams_site();
443 495
444 496 /**
445 497 * Get hub blog id for P2 sites.
446 498 *
@@ -502,8 +554,20 @@
502 554 return false;
503 555 }
504 556
505 557 /**
558 + * Indicate whether this site was upgraded from a trial plan at some point.
559 + *
560 + * @return bool
561 + */
562 + public function was_upgraded_from_trial() {
563 + if ( function_exists( 'has_blog_sticker' ) ) {
564 + return has_blog_sticker( 'has-upgraded-from-ecommerce-trial' );
565 + }
566 + return false;
567 + }
568 +
569 + /**
506 570 * Detect whether a site has the WooCommerce plugin active.
507 571 *
508 572 * @see /wpcom/public.api/rest/sal/class.json-api-site-jetpack-shadow.php.
509 573 *
@@ -708,26 +772,29 @@
708 772 private function user_can_view_post( $post ) {
709 773 if ( ! $post || is_wp_error( $post ) ) {
710 774 return false;
711 775 }
712 -
713 - if ( 'inherit' === $post->post_status ) {
776 + // If the post is of status inherit, check if the parent exists ( different to 0 ) to check for the parent status object.
777 + if ( 'inherit' === $post->post_status && 0 !== (int) $post->post_parent ) {
714 778 $parent_post = get_post( $post->post_parent );
715 - $post_status_obj = get_post_status_object( $parent_post->post_status );
779 + $post_status_obj = $parent_post ? get_post_status_object( $parent_post->post_status ) : null;
716 780 } else {
717 781 $post_status_obj = get_post_status_object( $post->post_status );
718 782 }
719 783
720 - $authorized = (
721 - $post_status_obj->public ||
722 - ( is_user_logged_in() &&
723 - (
724 - ( $post_status_obj->protected && current_user_can( 'edit_post', $post->ID ) ) ||
725 - ( $post_status_obj->private && current_user_can( 'read_post', $post->ID ) ) ||
726 - ( 'trash' === $post->post_status && current_user_can( 'edit_post', $post->ID ) ) ||
727 - 'auto-draft' === $post->post_status
728 - )
729 - )
784 + $authorized = false;
785 +
786 + if ( $post_status_obj ) {
787 + $authorized = $post_status_obj->public
788 + || ( is_user_logged_in() && (
789 + ( $post_status_obj->protected && current_user_can( 'edit_post', $post->ID ) )
790 + || ( $post_status_obj->private && current_user_can( 'read_post', $post->ID ) )
791 + ) );
792 + }
793 +
794 + $authorized = $authorized || (
795 + ( 'trash' === $post->post_status && current_user_can( 'edit_post', $post->ID ) )
796 + || 'auto-draft' === $post->post_status
730 797 );
731 798
732 799 if ( ! $authorized ) {
733 800 return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
@@ -890,39 +957,32 @@
890 957 public function get_capabilities() {
891 958 $is_wpcom_blog_owner = wpcom_get_blog_owner() === (int) get_current_user_id();
892 959
893 960 return array(
894 - 'edit_pages' => current_user_can( 'edit_pages' ),
895 - 'edit_posts' => current_user_can( 'edit_posts' ),
896 - 'edit_others_posts' => current_user_can( 'edit_others_posts' ),
897 - 'edit_others_pages' => current_user_can( 'edit_others_pages' ),
898 - 'delete_posts' => current_user_can( 'delete_posts' ),
899 - 'delete_others_posts' => current_user_can( 'delete_others_posts' ),
900 - 'edit_theme_options' => current_user_can( 'edit_theme_options' ),
901 - 'edit_users' => current_user_can( 'edit_users' ),
902 - 'list_users' => current_user_can( 'list_users' ),
903 - 'manage_categories' => current_user_can( 'manage_categories' ),
904 - 'manage_options' => current_user_can( 'manage_options' ),
905 - 'moderate_comments' => current_user_can( 'moderate_comments' ),
961 + 'edit_pages' => $this->current_user_can( 'edit_pages' ),
962 + 'edit_posts' => $this->current_user_can( 'edit_posts' ),
963 + 'edit_others_posts' => $this->current_user_can( 'edit_others_posts' ),
964 + 'edit_others_pages' => $this->current_user_can( 'edit_others_pages' ),
965 + 'delete_posts' => $this->current_user_can( 'delete_posts' ),
966 + 'delete_others_posts' => $this->current_user_can( 'delete_others_posts' ),
967 + 'edit_theme_options' => $this->current_user_can( 'edit_theme_options' ),
968 + 'edit_users' => $this->current_user_can( 'edit_users' ),
969 + 'list_users' => $this->current_user_can( 'list_users' ),
970 + 'manage_categories' => $this->current_user_can( 'manage_categories' ),
971 + 'manage_options' => $this->current_user_can( 'manage_options' ),
972 + 'moderate_comments' => $this->current_user_can( 'moderate_comments' ),
906 973 'activate_wordads' => $is_wpcom_blog_owner,
907 - 'promote_users' => current_user_can( 'promote_users' ),
908 - 'publish_posts' => current_user_can( 'publish_posts' ),
909 - 'upload_files' => current_user_can( 'upload_files' ),
910 - 'delete_users' => current_user_can( 'delete_users' ),
911 - 'remove_users' => current_user_can( 'remove_users' ),
974 + 'promote_users' => $this->current_user_can( 'promote_users' ),
975 + 'publish_posts' => $this->current_user_can( 'publish_posts' ),
976 + 'upload_files' => $this->current_user_can( 'upload_files' ),
977 + 'delete_users' => $this->current_user_can( 'delete_users' ),
978 + 'remove_users' => $this->current_user_can( 'remove_users' ),
912 979 'own_site' => $is_wpcom_blog_owner,
913 - /**
914 - * Filter whether the Hosting section in Calypso should be available for site.
915 - *
916 - * @module json-api
917 - *
918 - * @since 8.2.0
919 - *
920 - * @param bool $view_hosting Can site access Hosting section. Default to false.
921 - */
922 - 'view_hosting' => apply_filters( 'jetpack_json_api_site_can_view_hosting', false ),
923 980 'view_stats' => stats_is_blog_user( $this->blog_id ),
924 - 'activate_plugins' => current_user_can( 'activate_plugins' ),
981 + 'activate_plugins' => $this->current_user_can( 'activate_plugins' ),
982 + 'update_plugins' => $this->current_user_can( 'update_plugins' ),
983 + 'export' => $this->current_user_can( 'export' ),
984 + 'import' => $this->current_user_can( 'import' ),
925 985 );
926 986 }
927 987
928 988 /**
@@ -954,9 +1014,9 @@
954 1014 **/
955 1015 public function get_logo() {
956 1016 // Set an empty response array.
957 1017 $logo_setting = array(
958 - 'id' => (int) 0,
1018 + 'id' => 0,
959 1019 'sizes' => array(),
960 1020 'url' => '',
961 1021 );
962 1022
@@ -1017,8 +1077,31 @@
1017 1077 return get_option( 'stylesheet' );
1018 1078 }
1019 1079
1020 1080 /**
1081 + * Returns a list of errors for broken themes on the site.
1082 + *
1083 + * @return array
1084 + */
1085 + public function get_theme_errors() {
1086 + $themes_with_errors = wp_get_themes( array( 'errors' => true ) );
1087 + $theme_errors = array();
1088 +
1089 + foreach ( $themes_with_errors as $theme ) {
1090 + $errors = $theme->errors();
1091 +
1092 + if ( is_wp_error( $errors ) && ! empty( $errors->get_error_messages() ) ) {
1093 + $theme_errors[] = array(
1094 + 'name' => sanitize_title( $theme->get( 'Name' ) ),
1095 + 'errors' => (array) $errors->get_error_messages(),
1096 + );
1097 + }
1098 + }
1099 +
1100 + return $theme_errors;
1101 + }
1102 +
1103 + /**
1021 1104 * Gets the header image data.
1022 1105 *
1023 1106 * @return bool|object
1024 1107 **/
@@ -1172,9 +1255,9 @@
1172 1255 $ss = new Sharing_Service();
1173 1256 $blog_services = $ss->get_blog_services();
1174 1257 $default_sharing_status = ! empty( $blog_services['visible'] );
1175 1258 }
1176 - return (bool) $default_sharing_status;
1259 + return $default_sharing_status;
1177 1260 }
1178 1261
1179 1262 /**
1180 1263 * Displays the current comment status
@@ -1316,17 +1399,21 @@
1316 1399 return empty( $options['designType'] ) ? null : $options['designType'];
1317 1400 }
1318 1401
1319 1402 /**
1320 - * Returns the 'siteGoals' option if set (eg. share, promote, educate, sell, showcase), null otherwise.
1403 + * Returns the 'site_goals' option if set (eg. share, promote, educate, sell, showcase).
1321 1404 *
1322 - * @return string|null
1405 + * @return array
1323 1406 **/
1324 1407 public function get_site_goals() {
1325 - $options = get_option( 'options' );
1326 - return empty( $options['siteGoals'] ) ? null : $options['siteGoals'];
1408 + $site_goals_option = get_option( 'site_goals' );
1409 +
1410 + if ( is_array( $site_goals_option ) ) {
1411 + return $site_goals_option;
1412 + }
1413 +
1414 + return array();
1327 1415 }
1328 -
1329 1416 /**
1330 1417 * Return site's launch status. Expanded in class.json-api-site-jetpack.php.
1331 1418 *
1332 1419 * @return bool False in this case.
@@ -1432,17 +1519,68 @@
1432 1519 return false;
1433 1520 }
1434 1521
1435 1522 /**
1436 - * The site options for DIFM lite in the design picker step
1523 + * Get the DIFM Lite site options exposed to the frontend while a build is in progress.
1437 1524 *
1438 - * @return string
1525 + * Returns null unless the site has an active DIFM Lite build and the code is
1526 + * running on WordPress.com, where the DIFM Lite library exists.
1527 + *
1528 + * @return array|null
1439 1529 */
1440 1530 public function get_difm_lite_site_options() {
1441 - return get_option( 'difm_lite_site_options' );
1531 + if ( ! $this->is_difm_lite_in_progress() ) {
1532 + return null;
1533 + }
1534 + if ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM || ! function_exists( 'require_lib' ) ) {
1535 + return null;
1536 + }
1537 + require_lib( 'difm-lite' );
1538 + if ( ! class_exists( '\DIFM_Lite_Options' ) ) {
1539 + // Fail closed if the library did not define the class; this method
1540 + // documents null for every path where the options are unavailable.
1541 + return null;
1542 + }
1543 + // The submission state is canonical on the purchase blog. This site may be
1544 + // the stickered staging build target, whose own options blob is empty, so
1545 + // resolve to the purchase blog before reading — otherwise a post-submit
1546 + // staging target reports itself as pre-submit.
1547 + //
1548 + // resolve_purchase_site_id() ships in the wpcom DIFM Lite site-role
1549 + // pointers change. This Jetpack code can deploy before that lands, so
1550 + // fall back to the current blog when it is unavailable — no retargeted
1551 + // builds can exist until that change is live, so the current blog is the
1552 + // purchase blog in that window.
1553 + $purchase_blog_id = $this->blog_id;
1554 + // @phan-suppress-next-line PhanUndeclaredClassReference -- wpcom-only class, guarded above.
1555 + if ( method_exists( '\DIFM_Lite_Options', 'resolve_purchase_site_id' ) ) {
1556 + // @phan-suppress-next-line PhanUndeclaredClassMethod -- wpcom-only class, guarded above.
1557 + $purchase_blog_id = \DIFM_Lite_Options::resolve_purchase_site_id( $this->blog_id );
1558 + }
1559 + // @phan-suppress-next-line PhanUndeclaredClassMethod -- wpcom-only class, guarded above.
1560 + $difm_lite_options = new \DIFM_Lite_Options( $purchase_blog_id );
1561 + return array(
1562 + // @phan-suppress-next-line PhanUndeclaredClassProperty -- wpcom-only class, guarded above.
1563 + 'is_website_content_submitted' => (bool) $difm_lite_options->is_website_content_submitted,
1564 + );
1442 1565 }
1443 1566
1444 1567 /**
1568 + * Check if the site has the gating-business-q1 blog sticker.
1569 + *
1570 + * @return bool
1571 + */
1572 + public function is_gating_business_q1() {
1573 + if ( function_exists( 'has_blog_sticker' ) ) {
1574 + return has_blog_sticker( 'gating-business-q1' );
1575 + } elseif ( function_exists( 'wpcomsh_is_site_sticker_active' ) ) {
1576 + // For atomic sites
1577 + return wpcomsh_is_site_sticker_active( 'gating-business-q1' );
1578 + }
1579 + return false;
1580 + }
1581 +
1582 + /**
1445 1583 * Get the option of site intent which value is coming from the Hero Flow
1446 1584 *
1447 1585 * @return string
1448 1586 */
@@ -1450,8 +1588,17 @@
1450 1588 return get_option( 'site_intent', '' );
1451 1589 }
1452 1590
1453 1591 /**
1592 + * Get the option of site partner bundle which value is coming from the Partner Flow
1593 + *
1594 + * @return string
1595 + */
1596 + public function get_site_partner_bundle() {
1597 + return get_option( 'site_partner_bundle', '' );
1598 + }
1599 +
1600 + /**
1454 1601 * Get site option to determine if and how to display launchpad onboarding
1455 1602 *
1456 1603 * @return string
1457 1604 */
@@ -1459,8 +1606,17 @@
1459 1606 return get_option( 'launchpad_screen' );
1460 1607 }
1461 1608
1462 1609 /**
1610 + * Get the option onboarding_segment coming from the Guided Flow
1611 + *
1612 + * @return string
1613 + */
1614 + public function get_onboarding_segment() {
1615 + return get_option( 'onboarding_segment', '' );
1616 + }
1617 +
1618 + /**
1463 1619 * Get site option for completed launchpad checklist tasks
1464 1620 *
1465 1621 * @return string
1466 1622 */
@@ -1474,8 +1630,56 @@
1474 1630 return array();
1475 1631 }
1476 1632
1477 1633 /**
1634 + * Whether the AI Launchpad is enabled for this site, for the requesting user.
1635 + *
1636 + * The launchpad-personalization assignment is user-scoped: every site of an
1637 + * ai_launchpad user gets the AI Launchpad, however the site was created. Mirrors
1638 + * AI_Launchpad::is_enabled_for_site() in jetpack-mu-wpcom, so wp-admin and the
1639 + * Calypso-facing payload agree. Like the capabilities in this payload, the value
1640 + * is relative to the current user.
1641 + *
1642 + * @return bool
1643 + */
1644 + public function is_ai_launchpad_enabled() {
1645 + if ( (bool) get_option( 'wpcom_ai_launchpad_enabled' ) ) {
1646 + return true;
1647 + }
1648 +
1649 + return class_exists( '\Automattic\Jetpack\Jetpack_Mu_Wpcom\Launchpad_Personalization_Experiment' )
1650 + // @phan-suppress-next-line PhanUndeclaredClassMethod -- Lives in jetpack-mu-wpcom, outside this plugin's dependency graph; the class_exists guard above covers contexts where it isn't loaded.
1651 + && 'ai_launchpad' === \Automattic\Jetpack\Jetpack_Mu_Wpcom\Launchpad_Personalization_Experiment::get_variation();
1652 + }
1653 +
1654 + /**
1655 + * Whether the AI Launchpad was dismissed, reverting the site to the regular launchpad.
1656 + *
1657 + * @return bool
1658 + */
1659 + public function is_ai_launchpad_dismissed() {
1660 + return (bool) get_option( 'wpcom_ai_launchpad_dismissed' );
1661 + }
1662 +
1663 + /**
1664 + * Whether every AI Launchpad task has been completed.
1665 + *
1666 + * @return bool
1667 + */
1668 + public function is_ai_launchpad_completed() {
1669 + return (bool) get_option( 'wpcom_ai_launchpad_completed' );
1670 + }
1671 +
1672 + /**
1673 + * Get site option for migration source site domain
1674 + *
1675 + * @return string
1676 + */
1677 + public function get_migration_source_site_domain() {
1678 + return get_option( 'migration_source_site_domain', '' );
1679 + }
1680 +
1681 + /**
1478 1682 * Detect whether a site is WordPress.com Staging Site.
1479 1683 *
1480 1684 * @see class.json-api-site-jetpack.php for implementation.
1481 1685 */
@@ -1536,6 +1740,145 @@
1536 1740 }
1537 1741
1538 1742 $is_commercial = get_option( '_jetpack_site_is_commercial', null );
1539 1743 return $is_commercial === null ? null : (bool) $is_commercial;
1744 + }
1745 +
1746 + /**
1747 + * Returns an array of reasons why the site is considered commercial.
1748 + *
1749 + * @return array|null
1750 + */
1751 + public function get_is_commercial_reasons() {
1752 + $reasons = get_option( '_jetpack_site_is_commercial_reason', array() );
1753 +
1754 + // Add override as reason if blog has the commercial stickers.
1755 + if ( empty( $reasons ) && $this->is_commercial() ) {
1756 + return array( 'manual-override' );
1757 + } elseif ( ! is_array( $reasons ) ) {
1758 + return array();
1759 + }
1760 +
1761 + return $reasons;
1762 + }
1763 +
1764 + /**
1765 + * Returns the site's interface selection e.g. calypso vs. wp-admin
1766 + *
1767 + * @return string
1768 + **/
1769 + public function get_wpcom_admin_interface() {
1770 + return (string) get_option( 'wpcom_admin_interface' );
1771 + }
1772 +
1773 + /**
1774 + * Returns whether the site is part of the classic view early release.
1775 + *
1776 + * @return bool
1777 + **/
1778 + public function get_wpcom_classic_early_release() {
1779 + return ! empty( get_option( 'wpcom_classic_early_release' ) );
1780 + }
1781 +
1782 + /**
1783 + * Returns whether APM (Application Performance Monitoring) is enabled for the site.
1784 + *
1785 + * APM is an Atomic-only hosting feature. Non-Atomic site types (Simple wpcom, real
1786 + * Jetpack) return false; Jetpack_Shadow_Site overrides with the actual read.
1787 + *
1788 + * @return bool
1789 + **/
1790 + public function get_apm_enabled() {
1791 + return false;
1792 + }
1793 +
1794 + /**
1795 + * Get Zendesk site meta.
1796 + *
1797 + * @return array|null
1798 + */
1799 + abstract public function get_zendesk_site_meta();
1800 +
1801 + /**
1802 + * Detect whether there's a pending plan for this site.
1803 + *
1804 + * @return bool
1805 + */
1806 + abstract public function is_pending_plan();
1807 +
1808 + /**
1809 + * Detect whether the site is a Garden site.
1810 + *
1811 + * @return bool
1812 + */
1813 + public function is_garden() {
1814 + return false;
1815 + }
1816 +
1817 + /**
1818 + * Get the Garden name.
1819 + *
1820 + * @return string
1821 + */
1822 + public function garden_name() {
1823 + return null;
1824 + }
1825 +
1826 + /**
1827 + * Get the Garden partner.
1828 + *
1829 + * @return string
1830 + */
1831 + public function garden_partner() {
1832 + return null;
1833 + }
1834 +
1835 + /**
1836 + * Detect whether the Garden site is provisioned.
1837 + *
1838 + * @return bool|null
1839 + */
1840 + public function garden_is_provisioned() {
1841 + return null;
1842 + }
1843 +
1844 + /**
1845 + * Detect whether the site is a Flex site.
1846 + *
1847 + * @return bool
1848 + */
1849 + public function is_wpcom_flex() {
1850 + if ( function_exists( 'has_blog_sticker' ) ) {
1851 + return has_blog_sticker( 'flex-cache-site' );
1852 + }
1853 + return false;
1854 + }
1855 +
1856 + /**
1857 + * Detect whether Big Sky AI assistant is enabled for this site.
1858 + *
1859 + * @return bool
1860 + */
1861 + public function is_big_sky_enabled() {
1862 + return false;
1863 + }
1864 +
1865 + /**
1866 + * Get the state of any block on the site's outgoing email.
1867 + *
1868 + * @return array|null `status` (`blocked` or `at_risk`), `reason` and `expires_on`,
1869 + * or null if the site has never been blocked.
1870 + */
1871 + public function get_atomic_email_block() {
1872 + return null;
1873 + }
1874 +
1875 + /**
1876 + * Get Jetpack recovery mode status.
1877 + *
1878 + * @return array|null
1879 + */
1880 + public function get_jetpack_recovery_mode_status() {
1881 + $status = get_option( 'jetpack_recovery_mode_status' );
1882 + return is_array( $status ) ? $status : null;
1540 1883 }
1541 1884 }