import calendar import time from dataclasses import dataclass from typing import Dict, List, Optional, Tuple from django.conf import settings from django.http import HttpRequest from django.utils import translation from two_factor.utils import default_device from zerver.context_processors import get_apps_page_url from zerver.lib.events import do_events_register from zerver.lib.i18n import ( get_and_set_request_language, get_language_list, get_language_translation_data, ) from zerver.lib.narrow_helpers import NarrowTerm from zerver.lib.realm_description import get_realm_rendered_description from zerver.lib.request import RequestNotes from zerver.models import Message, Realm, Stream, UserProfile from zerver.views.message_flags import get_latest_update_message_flag_activity from zproject.config import get_config @dataclass class BillingInfo: show_billing: bool show_plans: bool sponsorship_pending: bool show_remote_billing: bool @dataclass class UserPermissionInfo: color_scheme: int is_guest: bool is_realm_admin: bool is_realm_owner: bool show_webathena: bool def get_furthest_read_time(user_profile: Optional[UserProfile]) -> Optional[float]: if user_profile is None: return time.time() user_activity = get_latest_update_message_flag_activity(user_profile) if user_activity is None: return None return calendar.timegm(user_activity.last_visit.utctimetuple()) def get_bot_types(user_profile: Optional[UserProfile]) -> List[Dict[str, object]]: bot_types: List[Dict[str, object]] = [] if user_profile is None: return bot_types for type_id, name in UserProfile.BOT_TYPES.items(): bot_types.append( dict( type_id=type_id, name=name, allowed=type_id in user_profile.allowed_bot_types, ) ) return bot_types def promote_sponsoring_zulip_in_realm(realm: Realm) -> bool: if not settings.PROMOTE_SPONSORING_ZULIP: return False # If PROMOTE_SPONSORING_ZULIP is enabled, advertise sponsoring # Zulip in the gear menu of non-paying organizations. return realm.plan_type in [Realm.PLAN_TYPE_STANDARD_FREE, Realm.PLAN_TYPE_SELF_HOSTED] def get_billing_info(user_profile: Optional[UserProfile]) -> BillingInfo: # See https://zulip.com/help/roles-and-permissions for clarity. show_billing = False show_plans = False sponsorship_pending = False # We want to always show the remote billing option as long as the user is authorized, # except on zulipchat.com where it's not applicable. show_remote_billing = ( (not settings.CORPORATE_ENABLED) and user_profile is not None and user_profile.has_billing_access ) # This query runs on home page load, so we want to avoid # hitting the database if possible. So, we only run it for the user # types that can actually see the billing info. if settings.CORPORATE_ENABLED and user_profile is not None and user_profile.has_billing_access: from corporate.models import CustomerPlan, get_customer_by_realm customer = get_customer_by_realm(user_profile.realm) if customer is not None: if customer.sponsorship_pending: sponsorship_pending = True if CustomerPlan.objects.filter(customer=customer).exists(): show_billing = True if user_profile.realm.plan_type == Realm.PLAN_TYPE_LIMITED: show_plans = True return BillingInfo( show_billing=show_billing, show_plans=show_plans, sponsorship_pending=sponsorship_pending, show_remote_billing=show_remote_billing, ) def get_user_permission_info(user_profile: Optional[UserProfile]) -> UserPermissionInfo: if user_profile is not None: return UserPermissionInfo( color_scheme=user_profile.color_scheme, is_guest=user_profile.is_guest, is_realm_owner=user_profile.is_realm_owner, is_realm_admin=user_profile.is_realm_admin, show_webathena=user_profile.realm.webathena_enabled, ) else: return UserPermissionInfo( color_scheme=UserProfile.COLOR_SCHEME_AUTOMATIC, is_guest=False, is_realm_admin=False, is_realm_owner=False, show_webathena=False, ) def build_page_params_for_home_page_load( request: HttpRequest, user_profile: Optional[UserProfile], realm: Realm, insecure_desktop_app: bool, narrow: List[NarrowTerm], narrow_stream: Optional[Stream], narrow_topic_name: Optional[str], needs_tutorial: bool, ) -> Tuple[int, Dict[str, object]]: """ This function computes page_params for when we load the home page. The page_params data structure gets sent to the client. """ client_capabilities = { "notification_settings_null": True, "bulk_message_deletion": True, "user_avatar_url_field_optional": True, "stream_typing_notifications": True, "user_settings_object": True, "linkifier_url_template": True, "user_list_incomplete": True, } if user_profile is not None: client = RequestNotes.get_notes(request).client assert client is not None state_data = do_events_register( user_profile, realm, client, apply_markdown=True, client_gravatar=True, slim_presence=True, client_capabilities=client_capabilities, narrow=narrow, include_streams=False, ) queue_id = state_data["queue_id"] default_language = state_data["user_settings"]["default_language"] else: # The spectator client will be fetching the /register response # for spectators via the API. state_data = None queue_id = None default_language = realm.default_language if user_profile is None: request_language = request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME, default_language) else: request_language = get_and_set_request_language( request, default_language, translation.get_language_from_path(request.path_info), ) furthest_read_time = get_furthest_read_time(user_profile) two_fa_enabled = settings.TWO_FACTOR_AUTHENTICATION_ENABLED and user_profile is not None billing_info = get_billing_info(user_profile) user_permission_info = get_user_permission_info(user_profile) # Pass parameters to the client-side JavaScript code. # These end up in a JavaScript Object named 'page_params'. # # Sync this with home_params_schema in base_page_params.ts. page_params: Dict[str, object] = dict( page_type="home", ## Server settings. test_suite=settings.TEST_SUITE, insecure_desktop_app=insecure_desktop_app, login_page=settings.HOME_NOT_LOGGED_IN, warn_no_email=settings.WARN_NO_EMAIL, # Only show marketing email settings if on Zulip Cloud corporate_enabled=settings.CORPORATE_ENABLED, ## Misc. extra data. language_list=get_language_list(), needs_tutorial=needs_tutorial, furthest_read_time=furthest_read_time, bot_types=get_bot_types(user_profile), two_fa_enabled=two_fa_enabled, apps_page_url=get_apps_page_url(), show_billing=billing_info.show_billing, show_remote_billing=billing_info.show_remote_billing, promote_sponsoring_zulip=promote_sponsoring_zulip_in_realm(realm), show_plans=billing_info.show_plans, sponsorship_pending=billing_info.sponsorship_pending, show_webathena=user_permission_info.show_webathena, # Adding two_fa_enabled as condition saves us 3 queries when # 2FA is not enabled. two_fa_enabled_user=two_fa_enabled and bool(default_device(user_profile)), is_spectator=user_profile is None, # There is no event queue for spectators since # events support for spectators is not implemented yet. no_event_queue=user_profile is None, server_sentry_dsn=settings.SENTRY_FRONTEND_DSN, ) if settings.SENTRY_FRONTEND_DSN is not None: page_params["realm_sentry_key"] = realm.string_id page_params["server_sentry_environment"] = get_config( "machine", "deploy_type", "development" ) page_params["server_sentry_sample_rate"] = settings.SENTRY_FRONTEND_SAMPLE_RATE page_params["server_sentry_trace_rate"] = settings.SENTRY_FRONTEND_TRACE_RATE page_params["state_data"] = state_data if narrow_stream is not None and state_data is not None: # In narrow_stream context, initial pointer is just latest message recipient = narrow_stream.recipient state_data["max_message_id"] = -1 max_message = ( # Uses index: zerver_message_realm_recipient_id Message.objects.filter(realm_id=realm.id, recipient=recipient) .order_by("-id") .only("id") .first() ) if max_message: state_data["max_message_id"] = max_message.id page_params["narrow_stream"] = narrow_stream.name if narrow_topic_name is not None: page_params["narrow_topic"] = narrow_topic_name page_params["narrow"] = [ dict(operator=term.operator, operand=term.operand) for term in narrow ] assert isinstance(state_data["user_settings"], dict) state_data["user_settings"]["enable_desktop_notifications"] = False page_params["translation_data"] = get_language_translation_data(request_language) if user_profile is None: # Get rendered version of realm description which is displayed in right # sidebar for spectator. page_params["realm_rendered_description"] = get_realm_rendered_description(realm) page_params["language_cookie_name"] = settings.LANGUAGE_COOKIE_NAME return queue_id, page_params