2020-07-18 20:33:28 +02:00
|
|
|
import calendar
|
|
|
|
import time
|
2020-07-18 18:13:59 +02:00
|
|
|
from dataclasses import dataclass
|
2022-06-21 22:53:25 +02:00
|
|
|
from typing import Dict, List, Optional, Tuple
|
2020-07-18 18:13:59 +02:00
|
|
|
|
|
|
|
from django.conf import settings
|
2020-07-18 20:33:28 +02:00
|
|
|
from django.http import HttpRequest
|
|
|
|
from django.utils import translation
|
|
|
|
from two_factor.utils import default_device
|
2020-07-18 18:13:59 +02:00
|
|
|
|
2021-06-14 12:38:43 +02:00
|
|
|
from zerver.context_processors import get_apps_page_url
|
2020-07-18 20:33:28 +02:00
|
|
|
from zerver.lib.events import do_events_register
|
|
|
|
from zerver.lib.i18n import (
|
2020-10-02 14:23:45 +02:00
|
|
|
get_and_set_request_language,
|
2020-07-18 20:33:28 +02:00
|
|
|
get_language_list,
|
|
|
|
get_language_translation_data,
|
|
|
|
)
|
2023-06-30 02:48:22 +02:00
|
|
|
from zerver.lib.narrow_helpers import NarrowTerm
|
2022-02-15 10:58:53 +01:00
|
|
|
from zerver.lib.realm_description import get_realm_rendered_description
|
2021-08-21 19:24:20 +02:00
|
|
|
from zerver.lib.request import RequestNotes
|
2020-07-18 20:33:28 +02:00
|
|
|
from zerver.models import Message, Realm, Stream, UserProfile
|
|
|
|
from zerver.views.message_flags import get_latest_update_message_flag_activity
|
2020-07-18 18:13:59 +02:00
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class BillingInfo:
|
|
|
|
show_billing: bool
|
|
|
|
show_plans: bool
|
2023-11-04 14:24:04 +01:00
|
|
|
sponsorship_pending: bool
|
2023-11-15 22:44:24 +01:00
|
|
|
show_remote_billing: bool
|
2020-07-18 18:13:59 +02:00
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class UserPermissionInfo:
|
|
|
|
color_scheme: int
|
|
|
|
is_guest: bool
|
|
|
|
is_realm_admin: bool
|
|
|
|
is_realm_owner: bool
|
|
|
|
show_webathena: bool
|
|
|
|
|
|
|
|
|
2020-07-18 20:33:28 +02:00
|
|
|
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]] = []
|
2020-09-27 06:49:16 +02:00
|
|
|
if user_profile is None:
|
2020-07-18 20:33:28 +02:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2021-03-05 18:39:02 +01:00
|
|
|
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.
|
2021-10-18 23:28:17 +02:00
|
|
|
return realm.plan_type in [Realm.PLAN_TYPE_STANDARD_FREE, Realm.PLAN_TYPE_SELF_HOSTED]
|
2021-03-05 18:39:02 +01:00
|
|
|
|
|
|
|
|
2021-06-15 14:24:35 +02:00
|
|
|
def get_billing_info(user_profile: Optional[UserProfile]) -> BillingInfo:
|
2023-11-04 14:24:04 +01:00
|
|
|
# See https://zulip.com/help/roles-and-permissions for clarity.
|
2020-07-18 18:13:59 +02:00
|
|
|
show_billing = False
|
|
|
|
show_plans = False
|
2023-11-04 14:24:04 +01:00
|
|
|
sponsorship_pending = False
|
2024-02-28 20:03:34 +01:00
|
|
|
|
|
|
|
# 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.
|
2023-11-15 22:44:24 +01:00
|
|
|
show_remote_billing = (
|
2024-02-28 20:03:34 +01:00
|
|
|
(not settings.CORPORATE_ENABLED)
|
|
|
|
and user_profile is not None
|
|
|
|
and user_profile.has_billing_access
|
2023-11-15 22:44:24 +01:00
|
|
|
)
|
|
|
|
|
2023-11-04 14:24:04 +01:00
|
|
|
# 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.
|
2023-11-21 17:36:11 +01:00
|
|
|
if settings.CORPORATE_ENABLED and user_profile is not None and user_profile.has_billing_access:
|
2023-11-04 14:24:04 +01:00
|
|
|
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:
|
2020-07-18 18:13:59 +02:00
|
|
|
show_plans = True
|
|
|
|
|
2021-03-05 18:39:02 +01:00
|
|
|
return BillingInfo(
|
|
|
|
show_billing=show_billing,
|
|
|
|
show_plans=show_plans,
|
2023-11-04 14:24:04 +01:00
|
|
|
sponsorship_pending=sponsorship_pending,
|
2023-11-15 22:44:24 +01:00
|
|
|
show_remote_billing=show_remote_billing,
|
2021-03-05 18:39:02 +01:00
|
|
|
)
|
2020-07-18 18:13:59 +02:00
|
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
)
|
2020-09-27 06:49:16 +02:00
|
|
|
else:
|
2020-07-18 18:13:59 +02:00
|
|
|
return UserPermissionInfo(
|
|
|
|
color_scheme=UserProfile.COLOR_SCHEME_AUTOMATIC,
|
|
|
|
is_guest=False,
|
|
|
|
is_realm_admin=False,
|
|
|
|
is_realm_owner=False,
|
|
|
|
show_webathena=False,
|
|
|
|
)
|
2020-07-18 20:33:28 +02:00
|
|
|
|
|
|
|
|
|
|
|
def build_page_params_for_home_page_load(
|
|
|
|
request: HttpRequest,
|
2020-09-27 06:49:16 +02:00
|
|
|
user_profile: Optional[UserProfile],
|
2020-10-02 00:00:28 +02:00
|
|
|
realm: Realm,
|
2020-07-18 20:33:28 +02:00
|
|
|
insecure_desktop_app: bool,
|
2023-06-30 00:50:04 +02:00
|
|
|
narrow: List[NarrowTerm],
|
2020-07-18 20:33:28 +02:00
|
|
|
narrow_stream: Optional[Stream],
|
2024-01-13 14:02:59 +01:00
|
|
|
narrow_topic_name: Optional[str],
|
2020-07-18 20:33:28 +02:00
|
|
|
needs_tutorial: bool,
|
2022-06-21 22:53:25 +02:00
|
|
|
) -> Tuple[int, Dict[str, object]]:
|
2020-07-18 20:33:28 +02:00
|
|
|
"""
|
|
|
|
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,
|
2021-01-04 16:21:54 +01:00
|
|
|
"stream_typing_notifications": True,
|
2021-07-28 16:00:58 +02:00
|
|
|
"user_settings_object": True,
|
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
|
|
|
"linkifier_url_template": True,
|
2023-11-28 18:24:43 +01:00
|
|
|
"user_list_incomplete": True,
|
2020-07-18 20:33:28 +02:00
|
|
|
}
|
|
|
|
|
2020-09-27 06:49:16 +02:00
|
|
|
if user_profile is not None:
|
2021-08-21 19:24:20 +02:00
|
|
|
client = RequestNotes.get_notes(request).client
|
2021-07-09 18:10:51 +02:00
|
|
|
assert client is not None
|
2024-02-13 02:56:26 +01:00
|
|
|
state_data = do_events_register(
|
2020-09-27 06:49:16 +02:00
|
|
|
user_profile,
|
2022-05-03 23:54:44 +02:00
|
|
|
realm,
|
2021-07-09 18:10:51 +02:00
|
|
|
client,
|
2020-09-27 06:49:16 +02:00
|
|
|
apply_markdown=True,
|
|
|
|
client_gravatar=True,
|
|
|
|
slim_presence=True,
|
2024-06-05 21:36:22 +02:00
|
|
|
presence_last_update_id_fetched_by_client=-1,
|
2020-09-27 06:49:16 +02:00
|
|
|
client_capabilities=client_capabilities,
|
|
|
|
narrow=narrow,
|
|
|
|
include_streams=False,
|
|
|
|
)
|
2024-02-19 07:45:19 +01:00
|
|
|
queue_id = state_data["queue_id"]
|
2024-02-13 02:56:26 +01:00
|
|
|
default_language = state_data["user_settings"]["default_language"]
|
2020-09-27 06:49:16 +02:00
|
|
|
else:
|
2022-05-03 23:22:41 +02:00
|
|
|
# The spectator client will be fetching the /register response
|
2024-02-19 07:45:19 +01:00
|
|
|
# for spectators via the API.
|
|
|
|
state_data = None
|
|
|
|
queue_id = None
|
2022-05-03 23:22:41 +02:00
|
|
|
default_language = realm.default_language
|
2020-07-18 20:33:28 +02:00
|
|
|
|
2022-05-02 11:13:03 +02:00
|
|
|
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),
|
|
|
|
)
|
2020-07-18 20:33:28 +02:00
|
|
|
|
2022-05-02 11:13:03 +02:00
|
|
|
furthest_read_time = get_furthest_read_time(user_profile)
|
2021-02-12 08:19:30 +01:00
|
|
|
two_fa_enabled = settings.TWO_FACTOR_AUTHENTICATION_ENABLED and user_profile is not None
|
2021-06-14 12:38:43 +02:00
|
|
|
billing_info = get_billing_info(user_profile)
|
|
|
|
user_permission_info = get_user_permission_info(user_profile)
|
2020-07-18 20:33:28 +02:00
|
|
|
|
|
|
|
# Pass parameters to the client-side JavaScript code.
|
2021-03-25 22:35:45 +01:00
|
|
|
# These end up in a JavaScript Object named 'page_params'.
|
2024-02-16 22:56:36 +01:00
|
|
|
#
|
|
|
|
# Sync this with home_params_schema in base_page_params.ts.
|
2022-06-21 22:53:25 +02:00
|
|
|
page_params: Dict[str, object] = dict(
|
2024-02-16 22:56:36 +01:00
|
|
|
page_type="home",
|
2021-05-20 20:14:17 +02:00
|
|
|
## Server settings.
|
2020-07-18 20:33:28 +02:00
|
|
|
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,
|
2021-04-28 00:25:27 +02:00
|
|
|
# Only show marketing email settings if on Zulip Cloud
|
2021-06-14 09:07:47 +02:00
|
|
|
corporate_enabled=settings.CORPORATE_ENABLED,
|
2021-05-20 20:14:17 +02:00
|
|
|
## Misc. extra data.
|
2020-07-18 20:33:28 +02:00
|
|
|
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,
|
2021-06-14 12:38:43 +02:00
|
|
|
apps_page_url=get_apps_page_url(),
|
|
|
|
show_billing=billing_info.show_billing,
|
2023-11-15 22:44:24 +01:00
|
|
|
show_remote_billing=billing_info.show_remote_billing,
|
2021-06-14 12:38:43 +02:00
|
|
|
promote_sponsoring_zulip=promote_sponsoring_zulip_in_realm(realm),
|
|
|
|
show_plans=billing_info.show_plans,
|
2023-11-04 14:24:04 +01:00
|
|
|
sponsorship_pending=billing_info.sponsorship_pending,
|
2021-06-14 12:38:43 +02:00
|
|
|
show_webathena=user_permission_info.show_webathena,
|
2020-07-18 20:33:28 +02:00
|
|
|
# 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)),
|
2021-06-15 18:03:32 +02:00
|
|
|
is_spectator=user_profile is None,
|
|
|
|
# There is no event queue for spectators since
|
|
|
|
# events support for spectators is not implemented yet.
|
2020-09-27 06:49:16 +02:00
|
|
|
no_event_queue=user_profile is None,
|
2020-07-18 20:33:28 +02:00
|
|
|
)
|
|
|
|
|
2024-02-13 02:56:26 +01:00
|
|
|
page_params["state_data"] = state_data
|
2020-07-18 20:33:28 +02:00
|
|
|
|
2024-02-19 07:45:19 +01:00
|
|
|
if narrow_stream is not None and state_data is not None:
|
2020-07-18 20:33:28 +02:00
|
|
|
# In narrow_stream context, initial pointer is just latest message
|
|
|
|
recipient = narrow_stream.recipient
|
2024-02-13 02:56:26 +01:00
|
|
|
state_data["max_message_id"] = -1
|
2023-08-30 21:19:37 +02:00
|
|
|
max_message = (
|
|
|
|
# Uses index: zerver_message_realm_recipient_id
|
|
|
|
Message.objects.filter(realm_id=realm.id, recipient=recipient)
|
|
|
|
.order_by("-id")
|
|
|
|
.only("id")
|
|
|
|
.first()
|
|
|
|
)
|
2023-08-30 18:55:46 +02:00
|
|
|
if max_message:
|
2024-02-13 02:56:26 +01:00
|
|
|
state_data["max_message_id"] = max_message.id
|
2020-07-18 20:33:28 +02:00
|
|
|
page_params["narrow_stream"] = narrow_stream.name
|
2024-01-13 14:02:59 +01:00
|
|
|
if narrow_topic_name is not None:
|
|
|
|
page_params["narrow_topic"] = narrow_topic_name
|
2023-06-30 00:50:04 +02:00
|
|
|
page_params["narrow"] = [
|
|
|
|
dict(operator=term.operator, operand=term.operand) for term in narrow
|
|
|
|
]
|
2024-02-13 02:56:26 +01:00
|
|
|
assert isinstance(state_data["user_settings"], dict)
|
|
|
|
state_data["user_settings"]["enable_desktop_notifications"] = False
|
2020-07-18 20:33:28 +02:00
|
|
|
|
2020-09-24 13:04:54 +02:00
|
|
|
page_params["translation_data"] = get_language_translation_data(request_language)
|
2020-07-18 20:33:28 +02:00
|
|
|
|
2022-02-15 10:58:53 +01:00
|
|
|
if user_profile is None:
|
|
|
|
# Get rendered version of realm description which is displayed in right
|
|
|
|
# sidebar for spectator.
|
2022-05-05 06:50:20 +02:00
|
|
|
page_params["realm_rendered_description"] = get_realm_rendered_description(realm)
|
2022-05-02 11:13:03 +02:00
|
|
|
page_params["language_cookie_name"] = settings.LANGUAGE_COOKIE_NAME
|
2022-02-15 10:58:53 +01:00
|
|
|
|
2024-02-19 07:45:19 +01:00
|
|
|
return queue_id, page_params
|