7 use lib
"$ENV{LJHOME}/cgi-bin";
9 # load the bread crumb hash
13 use LJ
::Auth
::Challenge
;
16 use Class
::Autouse
qw(
18 LJ::Subscription::Pending
21 LJ::Directory::Constraint
27 use LJ
::URI
::Shortener
;
30 use Digest
::SHA qw
/sha1_base64/;
34 # des: Returns an HTML <img> or <input> tag to an named image
35 # code, which each site may define with a different image file with
36 # its own dimensions. This prevents hard-coding filenames & sizes
37 # into the source. The real image data is stored in LJ::Img, which
38 # has default values provided in cgi-bin/imageconf.pl but can be
39 # overridden in etc/ljconfig.pl.
40 # args: imagecode, type?, attrs?
41 # des-imagecode: The unique string key to reference the image. Not a filename,
42 # but the purpose or location of the image.
43 # des-type: By default, the tag returned is an <img> tag, but if 'type'
44 # is "input", then an input tag is returned.
45 # des-attrs: Optional hashref of other attributes. If this isn't a hashref,
46 # then it's assumed to be a scalar for the 'name' attribute for
52 my $type = shift; # either "" or "input"
58 if (ref $attr eq "HASH") {
59 $alt = LJ
::ehtml
($attr->{alt
}) if (exists $attr->{alt
});
60 foreach (keys %$attr) {
61 $attrs .= " $_=\"" . LJ
::ehtml
($attr->{$_}) . "\""
62 unless ((lc $_) eq 'alt');
65 $attrs = " name=\"$attr\"";
69 my $i = $LJ::Img
::img
{$ic};
70 $alt ||= LJ
::Lang
::string_exists
($i->{'alt'}) ? LJ
::Lang
::ml
($i->{'alt'}) : $i->{'alt'};
72 return "<img src=\"$LJ::IMGPREFIX$i->{'src'}\" width=\"$i->{'width'}\" ".
73 "height=\"$i->{'height'}\" alt=\"$alt\" title=\"$alt\" ".
74 "border='0'$attrs />";
76 if ($type eq "input") {
77 return "<input type=\"image\" src=\"$LJ::IMGPREFIX$i->{'src'}\" ".
78 "width=\"$i->{'width'}\" height=\"$i->{'height'}\" title=\"$alt\" ".
79 "alt=\"$alt\" border='0'$attrs />";
85 # name: LJ::date_to_view_links
87 # des: Returns HTML of date with links to user's journal.
89 # des-date: date in yyyy-mm-dd form.
90 # returns: HTML with yyyy, mm, and dd all links to respective views.
92 sub date_to_view_links
95 return unless $date =~ /^(\d\d\d\d)-(\d\d)-(\d\d)/;
97 my ($y, $m, $d) = ($1, $2, $3);
98 my ($nm, $nd) = ($m+0, $d+0); # numeric, without leading zeros
99 my $user = $u->{'user'};
100 my $base = LJ
::journal_base
($u);
103 $ret .= "<a href=\"$base/$y/\">$y</a>-";
104 $ret .= "<a href=\"$base/$y/$m/\">$m</a>-";
105 $ret .= "<a href=\"$base/$y/$m/$d/\">$d</a>";
111 # name: LJ::auto_linkify
112 # des: Takes a plain-text string and changes URLs into <a href> tags (auto-linkification).
114 # des-str: The string to perform auto-linkification on.
115 # returns: The auto-linkified text.
122 if ($str =~ /^(.*?)(&(#39|quot|lt|gt)(;.*)?)$/) {
123 return "<a href='$1'>$1</a>$2";
125 return "<a href='$str'>$str</a>";
128 $str =~ s!(https?://[^\s\'\"\<\>]+[a-zA-Z0-9_/&=\-])! $match->($1); !ge;
132 # return 1 if URL is a safe stylesheet that S1/S2/etc can pull in.
133 # return 0 to reject the link tag
134 # return a URL to rewrite the stylesheet URL
135 # $href will always be present. $host and $path may not.
136 sub valid_stylesheet_url
{
137 my ($href, $host, $path) = @_;
138 unless ($host && $path) {
139 return 0 unless $href =~ m!^https?://([^/]+?)(/.*)$!;
140 ($host, $path) = ($1, $2);
144 # allow tag, if we're doing no css cleaning
145 return 1 if $LJ::DISABLED
{'css_cleaner'};
147 # remove tag, if we have no CSSPROXY configured
148 return 0 unless $LJ::CSSPROXY
;
150 # rewrite tag for CSS cleaning
151 return "$LJ::CSSPROXY?u=" . LJ
::eurl
($href);
154 return 1 if $LJ::TRUSTED_CSS_HOST
{$host};
155 return $cleanit->() unless $host =~ /\Q$LJ::DOMAIN\E$/i;
157 # let users use system stylesheets.
158 return 1 if $host eq $LJ::DOMAIN
|| $host eq $LJ::DOMAIN_WEB
||
159 $href =~ /^\Q$LJ::STATPREFIX\E/;
162 return 1 if $path =~ m!^(/\w+)?/res/(\d+)/stylesheet(\?\d+)?$!;
170 # name: LJ::make_authas_select
171 # des: Given a u object and some options, determines which users the given user
172 # can switch to. If the list exists, returns a select list and a submit
173 # button with labels. Otherwise returns a hidden element.
174 # returns: string of HTML elements
176 # des-opts: Optional. Valid keys are:
177 # 'authas' - current user, gets selected in drop-down;
178 # 'label' - label to go before form elements;
179 # 'button' - button label for submit button;
180 # others - arguments to pass to [func[LJ::get_authas_list]];
181 # check_paid - for each user in list will set parameter 'data-paid:0|1', used by JS
183 sub make_authas_select
{
184 my ($u, $opts) = @_; # type, authas, label, button
186 die "make_authas_select called outside of web context"
187 unless LJ
::is_web_context
();
189 my @list = LJ
::get_authas_list
($u, $opts);
191 # only do most of form if there are options to select from
192 shift @list if @list && $opts->{'remove_self'};
193 if (@list > 1 || $opts->{'show_me'} || $list[0] ne $u->{'user'}) {
195 my $label = $BML::ML
{'web.authas.label'};
196 $label = $BML::ML
{'web.authas.label.comm'} if ($opts->{'type'} eq "C");
197 $ret = ($opts->{'label'} || $label) . " ";
198 my %select_id = $opts->{'id'} ?
( id
=> $opts->{'id'} ) : ();
199 $ret .= LJ
::html_select
({ 'name' => 'authas',
200 'selected' => $opts->{'authas'} || $u->{'user'},
201 'class' => 'hideable',
204 ## We loaded all users in LJ::get_authas_list(). Here we use their singletons.
206 my $u = LJ
::load_user
($_);
207 my %is_paid = $opts->{'check_paid'}
208 ?
($u && ($u->get_cap('perm') || $u->get_cap('paid'))
209 ?
( js_data
=> " data-paid='1' " )
210 : ( js_data
=> " data-paid='0' " )
215 value
=> $u->display_name,
218 } @list), @
{$opts->{'add_fields'}} ) . " ";
219 $ret .= $opts->{'button_tag'} . LJ
::html_submit
(undef, $opts->{'button'} || $BML::ML
{'web.authas.btn'}) . $opts->{'button_close_tag'};
223 # no communities to choose from, give the caller a hidden
224 my $ret = LJ
::html_hidden
('authas', $opts->{'authas'} || $u->{'user'});
225 $ret .= $opts->{'nocomms'} if $opts->{'nocomms'};
230 # name: LJ::make_postto_select
231 # des: Given a u object and some options, determines which users the given user
232 # can post to. If the list exists, returns a select list and a submit
233 # button with labels. Otherwise returns a hidden element.
234 # returns: string of HTML elements
236 # des-opts: Optional. Valid keys are:
237 # 'postto' - current user, gets selected in drop-down;
238 # 'label' - label to go before form elements;
239 # 'button' - button label for submit button;
240 # others - arguments to pass to [func[LJ::get_postto_list]].
242 sub make_postto_select
{
243 my ($u, $opts) = @_; # type, authas, label, button
245 my @list = LJ
::get_postto_list
($u, $opts);
247 # only do most of form if there are options to select from
249 return ($opts->{'label'} || $BML::ML
{'web.postto.label'}) . " " .
250 LJ
::html_select
({ 'name' => 'authas',
251 'selected' => $opts->{'authas'} || $u->{'user'}},
252 map { $_, $_ } @list) . " " .
253 LJ
::html_submit
(undef, $opts->{'button'} || $BML::ML
{'web.postto.btn'});
256 # no communities to choose from, give the caller a hidden
257 return LJ
::html_hidden
('authas', $opts->{'authas'} || $u->{'user'});
261 # name: LJ::help_icon
262 # des: Returns BML to show a help link/icon given a help topic, or nothing
263 # if the site hasn't defined a URL for that topic. Optional arguments
264 # include HTML/BML to place before and after the link/icon, should it
266 # args: topic, pre?, post?
267 # des-topic: Help topic key.
268 # See doc/ljconfig.pl.txt, or [special[helpurls]] for examples.
269 # des-pre: HTML/BML to place before the help icon.
270 # des-post: HTML/BML to place after the help icon.
277 return "" unless (defined $LJ::HELPURL
{$topic});
278 return "$pre<?help $LJ::HELPURL{$topic} help?>$post";
281 # like help_icon, but no BML.
284 my $url = $LJ::HELPURL
{$topic} or return "";
285 my $pre = shift || "";
286 my $post = shift || "";
287 my $title = shift || "";
288 my $helplink_class = ($title) ?
"b-helplink b-helplink-withtitle" : "b-helplink";
289 my $title_wrapper = ($title) ?
"<span class=\"b-helplink-title\">$title</span>" : "";
290 # FIXME: use LJ::img() here, not hard-coding width/height
291 return "$pre<a href=\"$url\" class=\"$helplink_class\" target=\"_blank\" title=\"Help\"><span class=\"b-helplink-icon\"></span>$title_wrapper</a>$post";
295 # name: LJ::bad_input
296 # des: Returns common BML for reporting form validation errors in
298 # returns: BML showing errors.
300 # des-error: A list of errors
306 $ret .= "<?badcontent?>\n<ul>\n";
307 foreach my $ei (@errors) {
308 my $err = LJ
::errobj
($ei) or next;
310 $ret .= $err->as_bullets;
318 # name: LJ::error_list
319 # des: Returns an error bar with bulleted list of errors.
320 # returns: BML showing errors.
322 # des-error: A list of errors
326 # FIXME: retrofit like bad_input above? merge? make aliases for each other?
329 $ret .= '<div class="errorbar">';
331 $ret .= BML
::ml
('error.procrequest');
332 $ret .= "</strong><ul>";
334 foreach my $ei (@errors) {
335 my $err = LJ
::errobj
($ei) or next;
337 $ret .= $err->as_bullets;
339 $ret .= " </ul></div>";
345 # name: LJ::error_noremote
346 # des: Returns an error telling the user to log in.
347 # returns: Translation string "error.notloggedin"
351 return "<?needlogin?>";
356 # name: LJ::warning_list
357 # des: Returns a warning bar with bulleted list of warnings.
358 # returns: BML showing warnings
360 # des-warnings: A list of warnings
367 $ret .= "<?warningbar ";
369 $ret .= BML
::ml
('label.warning');
370 $ret .= "</strong><ul>";
372 foreach (@warnings) {
373 $ret .= "<li>$_</li>";
375 $ret .= " </ul> warningbar?>";
379 sub tosagree_widget
{
380 my ($checked, $errstr) = @_;
383 "<div class='formitemDesc'>" .
384 BML
::ml
('tos.mustread',
385 { aopts
=> "target='_new' href='$LJ::SITEROOT/legal/tos.bml'" }) .
387 "<iframe width='684' height='300' src='/legal/tos-mini.bml' " .
388 "style='border: 1px solid gray;'></iframe>" .
389 "<div>" . LJ
::html_check
({ name
=> 'agree_tos', id
=> 'agree_tos',
390 value
=> '1', selected
=> $checked }) .
391 "<label for='agree_tos'>" . BML
::ml
('tos.haveread') . "</label></div>" .
392 ($errstr ?
"<?inerr $errstr inerr?>" : '');
398 my $ret = "<?h1 $LJ::REQUIRED_TOS{title} h1?>";
400 my $html_str = LJ
::tosagree_str
($domain => 'html');
401 $ret .= "<?p $html_str p?>" if $html_str;
403 $ret .= "<div style='margin-left: 40px; margin-bottom: 20px;'>";
404 $ret .= LJ
::tosagree_widget
(@_);
411 my ($domain, $key) = @_;
413 return ref $LJ::REQUIRED_TOS
{$domain} && $LJ::REQUIRED_TOS
{$domain}->{$key} ?
414 $LJ::REQUIRED_TOS
{$domain}->{$key} : $LJ::REQUIRED_TOS
{$key};
419 # des: Cookies should only show pages which make no action.
420 # When an action is being made, check the request coming
421 # from the remote user is a POST request.
422 # info: When web pages are using cookie authentication, you can't just trust that
423 # the remote user wants to do the action they're requesting. It's way too
424 # easy for people to force other people into making GET requests to
425 # a server. What if a user requested http://server/delete_all_journal.bml,
426 # and that URL checked the remote user and immediately deleted the whole
427 # journal? Now anybody has to do is embed that address in an image
428 # tag and a lot of people's journals will be deleted without them knowing.
429 # Cookies should only show pages which make no action. When an action is
430 # being made, check that it's a POST request.
431 # returns: true if REQUEST_METHOD == "POST"
435 return (BML
::get_method
() eq "POST");
439 # name: LJ::robot_meta_tags
440 # des: Returns meta tags to instruct a robot/crawler to not index or follow links.
441 # returns: A string with appropriate meta tags
445 return "<meta name=\"robots\" content=\"noindex, nofollow, noarchive\" />\n" .
446 "<meta name=\"googlebot\" content=\"noindex, nofollow, noarchive, nosnippet\" />\n";
451 my ($page, $pages, $opts) = @_;
453 my $self_link = $opts->{'self_link'} ||
454 sub { BML
::self_link
({ 'page' => $_[0] }) };
456 my $href_opts = $opts->{'href_opts'} || sub { '' };
460 $navcrap .= "<center><font face='Arial,Helvetica' size='-1'><b>";
461 $navcrap .= BML
::ml
('ljlib.pageofpages',{'page'=>$page, 'total'=>$pages}) . "<br />";
462 my $left = "<b><<</b>";
463 if ($page > 1) { $left = "<a href='" . $self_link->($page-1) . "'" . $href_opts->($page-1) . ">$left</a>"; }
464 my $right = "<b>>></b>";
465 if ($page < $pages) { $right = "<a href='" . $self_link->($page+1) . "'" . $href_opts->($page+1) . ">$right</a>"; }
466 $navcrap .= $left . " ";
467 for (my $i=1; $i<=$pages; $i++) {
469 if ($i != $page) { $link = "<a href='" . $self_link->($i) . "'" . $href_opts->($i) . ">$link</a>"; }
470 else { $link = "<font size='+1'><b>$link</b></font>"; }
471 $navcrap .= "$link ";
473 $navcrap .= "$right";
474 $navcrap .= "</font></center>\n";
475 $navcrap = BML
::fill_template
("standout", { 'DATA' => $navcrap });
482 # name: LJ::make_cookie
483 # des: Prepares cookie header lines.
484 # returns: An array of cookie lines.
485 # args: name, value, expires, path?, domain?
486 # des-name: The name of the cookie.
487 # des-value: The value to set the cookie to.
488 # des-expires: The time (in seconds) when the cookie is supposed to expire.
489 # Set this to 0 to expire when the browser closes. Set it to
490 # undef to delete the cookie.
491 # des-path: The directory path to bind the cookie to.
492 # des-domain: The domain (or domains) to bind the cookie to.
496 my ($name, $value, $expires, $path, $domain) = @_;
500 # let the domain argument be an array ref, so callers can set
501 # cookies in both .foo.com and foo.com, for some broken old browsers.
502 if ($domain && ref $domain eq "ARRAY") {
504 push(@cookies, LJ
::make_cookie
($name, $value, $expires, $path, $_));
509 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($expires);
512 my @day = qw{Sunday Monday Tuesday Wednesday Thursday Friday Saturday
};
513 my @month = qw{Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
};
515 $cookie = sprintf "%s=%s", LJ
::eurl
($name), LJ
::eurl
($value);
517 # this logic is confusing potentially
518 unless (defined $expires && $expires==0) {
519 $cookie .= sprintf "; expires=$day[$wday], %02d-$month[$mon]-%04d %02d:%02d:%02d GMT",
520 $mday, $year, $hour, $min, $sec;
523 $cookie .= "; path=$path" if $path;
524 $cookie .= "; domain=$domain" if $domain;
525 push(@cookies, $cookie);
531 $LJ::ACTIVE_CRUMB
= shift;
535 sub set_dynamic_crumb
537 my ($title, $parent) = @_;
538 $LJ::ACTIVE_CRUMB
= [ $title, $parent ];
543 my $thiscrumb = LJ
::get_crumb
(LJ
::get_active_crumb
());
544 return LJ
::get_crumb
($thiscrumb->[2]);
549 return $LJ::ACTIVE_CRUMB
;
554 my $cur = LJ
::get_active_crumb
();
557 # get crumb, fix it up, and then put it on the list
560 push @list, [ $cur->[0], '', $cur->[1], 'dynamic' ];
563 # just a regular crumb
564 my $crumb = LJ
::get_crumb
($cur);
566 last if $cur eq $crumb->[2];
570 # now get the next one we're going after
571 $cur = $crumb->[2]; # parent of this crumb
579 my $crumbkey = shift;
580 if (defined $LJ::CRUMBS_LOCAL
{$crumbkey}) {
581 return $LJ::CRUMBS_LOCAL
{$crumbkey};
583 return $LJ::CRUMBS
{$crumbkey};
588 # name: LJ::check_referer
590 # des: Checks if the user is coming from a given URI.
591 # args: uri?, referer?
592 # des-uri: string; the URI we want the user to come from.
593 # des-referer: string; the location the user is posting from.
594 # If not supplied, will be retrieved with BML::get_client_header.
595 # In general, you don't want to pass this yourself unless
596 # you already have it or know we can't get it from BML.
597 # returns: 1 if they're coming from that URI, else undef
600 my $uri = shift(@_) || '';
601 my $referer = shift(@_) || BML
::get_client_header
('Referer');
603 # get referer and check
604 return 1 unless $referer;
605 return 1 if $LJ::SITEROOT
&& $referer =~ m!^$LJ::SITEROOT$uri!;
606 return 1 if $LJ::DOMAIN
&& $referer =~ m!^http://$LJ::DOMAIN$uri!;
607 return 1 if $LJ::DOMAIN_WEB
&& $referer =~ m!^http://$LJ::DOMAIN_WEB$uri!;
608 return 1 if $LJ::USER_VHOSTS
&& $referer =~ m!^http://([A-Za-z0-9_\-]{1,15})\.$LJ::DOMAIN$uri!;
609 return 1 if $uri =~ m!^http://! && $referer eq $uri;
614 # name: LJ::repost_auth
616 # des: Creates an authentication token to be used later to verify that a form hidden field "repost"
617 # not modified by user
618 # args: type, username, url
619 # des-type: type of repost, see LJSUP-8061
620 # des-username: name of original poster
621 # des-url: url of original post
622 # returns: HTML hidden field to be inserted into the output of a page.
625 my ($type, $username, $url, $subject, $raw) = @_;
626 my $str = join( ':', map ( LJ
::eurl
($_), $type, $username, $url, $subject));
627 my $auth = Digest
::MD5
::md5_hex
( $str . $LJ::REPOST_SECRET
);
629 return $auth if $raw;
632 return LJ
::html_hidden
("repost_params", $str);
636 # name: LJ::form_auth
638 # des: Creates an authentication token to be used later to verify that a form
639 # submission came from a particular user.
641 # des-raw: boolean; If true, returns only the token (no HTML).
642 # returns: HTML hidden field to be inserted into the output of a page.
646 my $chal = $LJ::REQ_GLOBAL
{form_auth_chal
};
649 my $remote = LJ
::get_remote
();
650 my $id = $remote ?
$remote->id : 0;
651 my $sess = $remote && $remote->session ?
$remote->session->id : LJ
::UniqCookie
->current_uniq;
653 my $auth = join('-', LJ
::rand_chars
(10), $id, $sess);
654 $chal = LJ
::Auth
::Challenge
->generate(86400, $auth);
655 $LJ::REQ_GLOBAL
{form_auth_chal
} = $chal;
658 return $raw ?
$chal : LJ
::html_hidden
("lj_form_auth", $chal);
662 # name: LJ::check_form_auth
664 # des: Verifies form authentication created with [func[LJ::form_auth]].
665 # returns: Boolean; true if the current data in %POST is a valid form, submitted
666 # by the user in $remote using the current session,
667 # or false if the user has changed, the challenge has expired,
668 # or the user has changed session (logged out and in again, or something).
670 sub check_form_auth
{
672 my $formauth = $BMLCodeBlock::POST
{'lj_form_auth'};
673 if (ref $opts eq 'HASH') {
674 $formauth = $opts->{'lj_form_auth'} if defined $opts->{'lj_form_auth'};
676 $formauth = $opts if defined $opts;
679 return 0 unless $formauth;
681 my $remote = LJ
::get_remote
();
682 my $id = $remote ?
$remote->id : 0;
683 my $sess = $remote && $remote->session ?
$remote->session->id : LJ
::UniqCookie
->current_uniq;
685 # check the attributes are as they should be
686 my $attr = LJ
::get_challenge_attributes
($formauth);
687 my ($randchars, $chal_id, $chal_sess) = split(/\-/, $attr);
689 return 0 unless $id == $chal_id;
690 return 0 unless $sess eq $chal_sess;
692 # check the signature is good and not expired
693 return LJ
::Auth
::Challenge
->check($formauth, {
694 dont_check_count
=> !$opts->{'enable_check_count'},
699 # name: LJ::create_qr_div
701 # des: Creates the hidden div that stores the QuickReply form.
702 # returns: undef upon failure or HTML for the div upon success
703 # args: user, remote, ditemid, stylemine, userpic
704 # des-u: user object or userid for journal reply in.
705 # des-ditemid: ditemid for this comment.
706 # des-stylemine: if the user has specified style=mine for this page.
707 # des-userpic: alternate default userpic.
711 my ($user, $ditemid, $stylemine, $userpic, $viewing_thread, $text_hint) = @_;
712 my $u = LJ
::want_user
($user);
713 my $remote = LJ
::get_remote
();
714 return undef unless $u && $remote && $ditemid;
715 return undef if $remote->underage;
720 LJ
::load_user_props
($remote, "opt_no_quickreply");
721 return undef if $remote->{'opt_no_quickreply'};
723 $qrhtml .= "<div id='qrformdiv'><form id='qrform' name='qrform' method='POST' action='$LJ::SITEROOT/talkpost_do.bml'>";
724 $qrhtml .= LJ
::form_auth
();
726 my $stylemineuri = $stylemine ?
"style=mine&" : "";
727 my $basepath = LJ
::journal_base
($u) . "/$ditemid.html?${stylemineuri}";
730 if ($remote->is_identity && $remote->is_trusted_identity) {
731 $usertype = lc($remote->identity->short_code) . '_cookie';
733 $usertype = 'cookieuser';
736 $qrhtml .= LJ
::html_hidden
({'name' => 'replyto', 'id' => 'replyto', 'value' => ''},
737 {'name' => 'parenttalkid', 'id' => 'parenttalkid', 'value' => ''},
738 {'name' => 'journal', 'id' => 'journal', 'value' => $u->{'user'}},
739 {'name' => 'itemid', 'id' => 'itemid', 'value' => $ditemid},
740 {'name' => 'usertype', 'id' => 'usertype', 'value' => $usertype },
741 {'name' => 'qr', 'id' => 'qr', 'value' => '1'},
742 {'name' => 'cookieuser', 'id' => 'cookieuser', 'value' => $remote->{'user'}},
743 {'name' => 'dtid', 'id' => 'dtid', 'value' => ''},
744 {'name' => 'basepath', 'id' => 'basepath', 'value' => $basepath},
745 {'name' => 'stylemine', 'id' => 'stylemine', 'value' => $stylemine},
746 {'name' => 'viewing_thread', 'id' => 'viewing_thread', 'value' => $viewing_thread},
749 # rate limiting challenge
751 $qrhtml .= LJ
::html_hidden
("chrp1", LJ
::Talk
::generate_chrp1
($u->{userid
}, $ditemid));
754 # Start making the div itself
755 $qrhtml .= "<table style='border: 1px solid black'>";
756 $qrhtml .= "<tr valign='center'>";
757 $qrhtml .= "<td align='right'><b>".BML
::ml
('/talkpost.bml.opt.from')."</b></td><td align='left'>";
758 $qrhtml .= LJ
::ljuser
($remote->{'user'});
759 $qrhtml .= "</td><td align='center'>";
761 my (%userpicmap, $defaultpicurl);
766 LJ
::do_request
({ "mode" => "login",
767 "ver" => ($LJ::UNICODE ?
"1" : "0"),
768 "user" => $remote->{'user'},
770 'getpickwurls' => 1, },
771 \
%res, { "noauth" => 1, "userid" => $remote->{'userid'}}
774 if ($res{'pickw_count'}) {
775 $qrhtml .= BML
::ml
('/talkpost.bml.label.picturetouse2',
777 'aopts'=>"href='$LJ::SITEROOT/allpics.bml?user=$remote->{'user'}'"});
779 for (my $i=1; $i<=$res{'pickw_count'}; $i++) {
780 push @pics, $res{"pickw_$i"};
782 @pics = sort { lc($a) cmp lc($b) } @pics;
783 $qrhtml .= LJ
::html_select
({'name' => 'prop_picture_keyword',
784 'selected' => $userpic, 'id' => 'prop_picture_keyword', 'tabindex' => '8' },
785 ("", BML
::ml
('/talkpost.bml.opt.defpic'), map { ($_, $_) } @pics));
787 # userpic browse button
789 <input type
="button" id
="lj_userpicselect" value
="Browse" onclick
="QuickReply.userpicSelect()" tabindex
="9" />
790 } unless $LJ::DISABLED
{userpicselect
} || ! $remote->get_cap('userpicselect');
792 $qrhtml .= LJ
::help_icon_html
("userpics", " ");
794 foreach my $i (1 .. $res{'pickw_count'}) {
795 $userpicmap{$res{"pickw_$i"}} = $res{"pickwurl_$i"};
798 if (my $upi = $remote->userpic) {
799 $defaultpicurl = $upi->url;
804 $qrhtml .= "</td></tr>";
806 $qrhtml .= "<tr><td align='right' valign='top'>";
807 $qrhtml .= "<b>".BML
::ml
('/talkpost.bml.opt.subject')."</b></td>";
808 $qrhtml .= "<td colspan='2' align='left'>";
809 $qrhtml .= "<input class='textbox' type='text' size='50' maxlength='100' name='subject' id='subject' value='' tabindex='10' />";
811 $qrhtml .= "<div id=\"subjectCaptionText\">" . $text_hint . "</div>" if $text_hint;
813 $qrhtml .= "</td></tr>";
815 $qrhtml .= "<tr valign='top'>";
816 $qrhtml .= "<td align='right'><b>".BML
::ml
('/talkpost.bml.opt.message')."</b></td>";
817 $qrhtml .= "<td colspan='3' style='width: 90%'>";
819 $qrhtml .= "<textarea class='textbox' rows='10' cols='50' wrap='soft' name='body' id='body' style='width: 99%' tabindex='20'></textarea>";
820 $qrhtml .= "</td></tr>";
822 $qrhtml .= LJ
::run_hook
('extra_quickreply_rows', {
824 'ditemid' => $ditemid,
827 $qrhtml .= "<tr><td> </td>";
828 $qrhtml .= "<td colspan='3' align='left'>";
830 $qrhtml .= LJ
::html_submit
('submitpost', BML
::ml
('/talkread.bml.button.post'),
831 { 'id' => 'submitpost',
832 'raw' => q
|onclick
="if (QuickReply.check()){ QuickReply.submit() }" tabindex
='30' |,
835 $qrhtml .= " " . LJ
::html_submit
('submitmoreopts', BML
::ml
('/talkread.bml.button.more'),
836 { 'id' => 'submitmoreopts', 'tabindex' => '31',
837 'raw' => 'onclick="if (QuickReply.more()){ QuickReply.submit() }"'
840 $qrhtml .= " <input type='checkbox' name='do_spellcheck' value='1' id='do_spellcheck' tabindex='32' /> <label for='do_spellcheck'>";
841 $qrhtml .= BML
::ml
('/talkread.bml.qr.spellcheck');
842 $qrhtml .= "</label>";
845 LJ
::load_user_props
($u, 'opt_logcommentips');
846 if ($u->{'opt_logcommentips'} eq 'A') {
847 $qrhtml .= '<p class="b-bubble b-bubble-alert b-bubble-noarrow b-bubble-intext b-qrform-alert-logcommentips">';
848 $qrhtml .= LJ
::deemp
(BML
::ml
('/talkpost.bml.logyourip'));
849 $qrhtml .= LJ
::help_icon_html
("iplogging", " ");
853 $qrhtml .= "</td></tr></table>";
854 $qrhtml .= "</form></div>";
857 $ret = "<script type=\"text/javascript\">\n";
859 $qrhtml = LJ
::ejs
($qrhtml);
861 # here we create some separate fields for saving the quickreply entry
862 # because the browser will not save to a dynamically-created form.
864 my $qrsaveform .= LJ
::ejs
(LJ
::html_hidden
(
865 {'name' => 'saved_subject', 'id' => 'saved_subject'},
866 {'name' => 'saved_body', 'id' => 'saved_body'},
867 {'name' => 'saved_spell', 'id' => 'saved_spell'},
868 {'name' => 'saved_upic', 'id' => 'saved_upic'},
869 {'name' => 'saved_dtid', 'id' => 'saved_dtid'},
870 {'name' => 'saved_ptid', 'id' => 'saved_ptid'},
873 %userpicmap = map { (LJ
::ehtml
($_) => $userpicmap{$_}) } keys %userpicmap;
875 my $userpicmap = LJ
::JSON
->to_json(\
%userpicmap);
877 var userpicmap
= $userpicmap;
878 var defaultpicurl
= "$defaultpicurl";
879 document
.write("$qrsaveform");
880 var de
= document
.createElement
('div');
882 de
.innerHTML
= "$qrhtml";
883 de
.style
.display
= 'none';
884 document
.body
.insertBefore
(de
, document
.body
.firstChild
);
893 # name: LJ::make_qr_link
895 # des: Creates the link to toggle the QR reply form or if
896 # JavaScript is not enabled, then forwards the user through
898 # returns: undef upon failure or HTML for the link
899 # args: dtid, basesubject, linktext, replyurl
900 # des-dtid: dtalkid for this comment
901 # des-basesubject: parent comment's subject
902 # des-linktext: text for the user to click
903 # des-replyurl: URL to forward user to if their browser
904 # does not support QR.
908 my ($dtid, $basesubject, $linktext, $replyurl) = @_;
910 return undef unless defined $dtid && $linktext && $replyurl;
912 my $remote = LJ
::get_remote
();
913 LJ
::load_user_props
($remote, "opt_no_quickreply");
914 unless ($remote->{'opt_no_quickreply'}) {
915 my $pid = int($dtid / 256);
917 $basesubject =~ s/^(Re:\s*)*//i;
918 $basesubject = "Re: $basesubject" if $basesubject;
919 $basesubject = LJ
::ehtml
(LJ
::ejs
($basesubject));
920 my $onclick = "return QuickReply.reply('$dtid',$pid,'$basesubject')";
923 $ju = LJ
::load_userid
(LJ
::Request
->notes('journalid')) if LJ
::Request
->is_inited and LJ
::Request
->notes('journalid');
925 $onclick = "" if $ju->{'opt_whocanreply'} eq 'friends' and $remote and not LJ
::is_friend
($ju, $remote);
926 return "<a href=\"$replyurl\" onclick=\"$onclick\" rel='nofollow'>$linktext</a>";
927 } else { # QR Disabled
928 return "<a href=\"$replyurl\" rel='nofollow'>$linktext</a>";
933 # name: LJ::get_lastcomment
935 # des: Looks up the last talkid and journal the remote user posted in.
936 # returns: talkid, jid
939 sub get_lastcomment
{
940 my $remote = LJ
::get_remote
();
941 return (undef, undef) unless $remote;
943 # Figure out their last post
944 my $memkey = [$remote->{'userid'}, "lastcomm:$remote->{'userid'}"];
945 my $memval = LJ
::MemCache
::get
($memkey);
947 ($jid, $talkid) = split(/:/, $memval) if $memval;
949 return ($talkid, $jid);
953 # name: LJ::make_qr_target
955 # des: Returns a div usable for QuickReply boxes.
956 # returns: HTML for the div
962 return "<div id='ljqrt$name' name='ljqrt$name'></div>";
966 # name: LJ::set_lastcomment
968 # des: Sets the lastcomm memcached key for this user's last comment.
969 # returns: undef on failure
970 # args: u, remote, dtalkid, life?
971 # des-u: Journal they just posted in, either u or userid
972 # des-remote: Remote user
973 # des-dtalkid: Talkid for the comment they just posted
974 # des-life: How long, in seconds, the memcached key should live.
978 my ($u, $remote, $dtalkid, $life) = @_;
980 my $userid = LJ
::want_userid
($u);
981 return undef unless $userid && $remote && $dtalkid;
983 # By default, this key lasts for 10 seconds.
986 # Set memcache key for highlighting the comment
987 my $memkey = [$remote->{'userid'}, "lastcomm:$remote->{'userid'}"];
988 LJ
::MemCache
::set
($memkey, "$userid:$dtalkid", time()+$life);
994 "<span class='de'>$_[0]</span>";
998 # name: LJ::entry_form
1000 # des: Returns a properly formatted form for creating/editing entries.
1001 # args: head, onload, opts
1002 # des-head: string reference for the <head> section (JavaScript previews, etc).
1003 # des-onload: string reference for JavaScript functions to be called on page load
1004 # des-opts: hashref of keys/values:
1005 # mode: either "update" or "edit", depending on context;
1006 # datetime: date and time, formatted yyyy-mm-dd hh:mm;
1007 # remote: remote u object;
1008 # subject: entry subject;
1009 # event: entry text;
1010 # richtext: allow rich text formatting;
1011 # auth_as_remote: bool option to authenticate as remote user, pre-filling pic/friend groups/etc.
1012 # return: form to include in BML pages.
1015 my $widget = LJ
::Widget
::EntryForm
->new;
1017 $widget->set_data(@_);
1018 return $widget->render;
1021 # entry form subject
1022 sub entry_form_subject_widget
{
1026 $class = qq { class="$class" };
1028 return qq { <input name
="subject" $class/> };
1031 # entry form hidden date field
1032 sub entry_form_date_widget
{
1033 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
1035 $mon=sprintf("%02d", $mon+1);
1036 $mday=sprintf("%02d", $mday);
1037 $min=sprintf("%02d", $min);
1038 return LJ
::html_hidden
({'name' => 'date_ymd_yyyy', 'value' => $year, 'id' => 'update_year'},
1039 {'name' => 'date_ymd_dd', 'value' => $mday, 'id' => 'update_day'},
1040 {'name' => 'date_ymd_mm', 'value' => $mon, 'id' => 'update_mon'},
1041 {'name' => 'hour', 'value' => $hour, 'id' => 'update_hour'},
1042 {'name' => 'min', 'value' => $min, 'id' => 'update_min'});
1045 # entry form event text box
1046 sub entry_form_entry_widget
{
1050 $class = qq { class="$class" };
1053 return qq { <textarea cols
=50 rows
=10 name
="event" $class></textarea
> };
1057 # entry form "journals can post to" dropdown
1058 # NOTE!!! returns undef if no other journals user can post to
1059 sub entry_form_postto_widget
{
1062 return undef unless LJ
::isu
($remote);
1065 # log in to get journals can post to
1067 $res = LJ
::Protocol
::do_request
("login", {
1068 "ver" => $LJ::PROTOCOL_VER
,
1069 "username" => $remote->{'user'},
1075 return undef unless $res;
1077 my @journals = map { $_, $_ } @
{$res->{'usejournals'}};
1079 return undef unless @journals;
1081 push @journals, $remote->{'user'};
1082 push @journals, $remote->{'user'};
1083 @journals = sort @journals;
1084 $ret .= LJ
::html_select
({ 'name' => 'usejournal', 'selected' => $remote->{'user'}}, @journals) . "\n";
1088 sub entry_form_security_widget
{
1091 my @secs = ("public", BML
::ml
('label.security.public'),
1092 "private", BML
::ml
('label.security.private'),
1093 "friends", BML
::ml
('label.security.friends'));
1095 $ret .= LJ
::html_select
({ 'name' => 'security'},
1101 sub entry_form_tags_widget
{
1104 return '' if $LJ::DISABLED
{tags
};
1106 $ret .= LJ
::html_text
({
1107 'name' => 'prop_taglist',
1109 'maxlength' => '255',
1111 $ret .= LJ
::help_icon
('addtags');
1117 # name: LJ::entry_form_decode
1119 # des: Decodes an entry_form into a protocol-compatible hash.
1120 # info: Generate form with [func[LJ::entry_form]].
1122 # des-req: protocol request hash to build.
1123 # des-post: entry_form POST contents.
1126 sub entry_form_decode
1128 my ($req, $POST) = @_;
1133 if ($POST->{'security'} eq "private") {
1135 } elsif ($POST->{'security'} eq "friends") {
1136 $sec = "usemask"; $amask = 1;
1137 } elsif ($POST->{'security'} eq "custom") {
1139 foreach my $bit (0..30) {
1140 next unless $POST->{"custom_bit_$bit"};
1141 $amask |= (1 << $bit);
1144 $req->{'security'} = $sec;
1145 $req->{'allowmask'} = $amask;
1148 my $date = LJ
::html_datetime_decode
({ 'name' => "date_ymd", }, $POST);
1149 my ($year, $mon, $day) = split( /\D/, $date);
1151 my $time_value= $POST->{'time'}; # get value of hh::mm
1152 my ($hour, $min) = split ( /:/, $time_value);
1154 # TEMP: ease golive by using older way of determining differences
1155 my $date_old = LJ
::html_datetime_decode
({ 'name' => "date_ymd_old", }, $POST);
1156 my ($year_old, $mon_old, $day_old) = split( /\D/, $date_old);
1157 my ($hour_old, $min_old) = ($POST->{'hour_old'}, $POST->{'min_old'});
1159 my $different = $POST->{'min_old'} && (($year ne $year_old) || ($mon ne $mon_old)
1160 || ($day ne $day_old) || ($hour ne $hour_old) || ($min ne $min_old));
1162 # this value is set when the JS runs, which means that the user-provided
1163 # time is sync'd with their computer clock. otherwise, the JS didn't run,
1164 # so let's guess at their timezone.
1165 if ($POST->{'date_diff'} || $POST->{'date_diff_nojs'} || $different) {
1166 $req->{'year'} = $year;
1167 $req->{'mon'} = $mon;
1168 $req->{'day'} = $day;
1169 $req->{'hour'} = $hour;
1170 $req->{'min'} = $min;
1173 # copy some things from %POST
1175 prop_picture_keyword prop_current_moodid
1177 prop_opt_screening prop_opt_noemail
1178 prop_opt_preformatted prop_opt_nocomments prop_opt_lockcomments
1179 prop_current_location prop_current_coords
1180 prop_taglist prop_qotdid prop_give_features
1181 prop_ljart_event_town prop_ljart_event_location
1182 prop_ljart_event_paid prop_ljart_event_price
1183 prop_ljart_event_type prop_ljart_event_image
1184 prop_ljart_event_desc prop_ljart_event prop_ljart_event_users
1185 prop_ljart_portfolio_thumbnail prop_ljart_portfolio
1186 repost_budget paid_repost_on repost_limit_sc
1187 repost_targeting_age repost_targeting_gender
1188 repost_targeting_country repost_targeting_state
1190 $req->{$_} = $POST->{$_};
1194 foreach (qw( prop_discovery )) {
1195 $req->{$_} = $POST->{$_} if defined $POST->{$_};
1198 if ( length( $POST->{'prop_current_music'} ) > 197 ) {
1199 my $pos = index( $POST->{'prop_current_music'}, '|' );
1202 $req->{'prop_current_music'} = substr( $POST->{'prop_current_music'}, 0, 197 ) . '...';
1205 $req->{'prop_current_music'} = substr( substr($POST->{'prop_current_music'}, 0, $pos), 0, 197 ) . '... ' . substr( $POST->{'prop_current_music'}, $pos );
1209 $req->{'prop_current_music'} = $POST->{'prop_current_music'};
1212 if ($POST->{"subject"} eq BML
::ml
('entryform.subject.hint2')) {
1213 $req->{"subject"} = "";
1216 $req->{"prop_opt_preformatted"} ||= $POST->{'switched_rte_on'} ?
1 :
1217 $POST->{'event_format'} eq "preformatted" ?
1 : 0;
1218 $req->{"prop_opt_nocomments"} ||= $POST->{'comment_settings'} eq "nocomments" ?
1 : 0;
1219 $req->{'prop_opt_lockcomments'} ||= $POST->{'comment_settings'} eq 'lockcomments' ?
1 : 0;
1220 $req->{"prop_opt_noemail"} ||= $POST->{'comment_settings'} eq "noemail" ?
1 : 0;
1221 $req->{'prop_opt_backdated'} = $POST->{'prop_opt_backdated'} ?
1 : 0;
1222 $req->{'prop_opt_norating'} = $POST->{'prop_opt_norating'} ?
1 : 0;
1224 my $ljart_date_from = $POST->{'prop_ljart_event_date_from'};
1225 my $ljart_date_to = $POST->{'prop_ljart_event_date_to'};
1226 my $ljart_time_from = $POST->{'prop_ljart_event_time_from'};
1227 my $ljart_time_to = $POST->{'prop_ljart_event_time_to'};
1229 $req->{'prop_ljart_event_date'} = $ljart_date_to ?
"$ljart_date_from-$ljart_date_to" : $ljart_date_from;
1230 $req->{'prop_ljart_event_time'} = $ljart_time_to ?
"$ljart_time_from-$ljart_time_to" : $ljart_time_from;
1232 $req->{'prop_copyright'} = $POST->{'prop_copyright'} ?
'P' : 'C' if LJ
::is_enabled
('default_copyright', LJ
::get_remote
())
1233 && $POST->{'defined_copyright'};
1234 $req->{'prop_poster_ip'} = LJ
::get_remote_ip
();
1236 my $uniq = LJ
::UniqCookie
->current_uniq();
1237 $req->{'prop_uniq'} = $uniq;
1239 if ( my $reposted_from = $POST->{'reposted_from'} ) {
1240 my $reposted_entry = LJ
::Entry
->new_from_url($reposted_from);
1242 #TODO: check visibility? it's not a security concern, but still
1243 if ($reposted_entry) {
1244 $req->{'prop_reposted_from'} = $reposted_entry->url;
1248 if (LJ
::is_enabled
("content_flag")) {
1249 $req->{prop_adult_content
} = $POST->{prop_adult_content
};
1250 $req->{prop_adult_content
} = ""
1251 unless $req->{prop_adult_content
} eq "none" || $req->{prop_adult_content
} eq "concepts" || $req->{prop_adult_content
} eq "explicit";
1254 # nuke taglists that are just blank
1255 $req->{'prop_taglist'} = "" unless defined $req->{'prop_taglist'} && $req->{'prop_taglist'} =~ /\S/;
1257 # Convert the rich text editor output back to parsable lj tags.
1258 my $event = $POST->{'event'};
1259 if ($POST->{'switched_rte_on'}) {
1260 $req->{"prop_used_rte"} = 1;
1262 # We want to see if we can hit the fast path for cleaning
1263 # if they did nothing but add line breaks.
1264 my $attempt = $event;
1265 $attempt =~ s!<br />!\n!g;
1267 if ($attempt !~ /<\w/) {
1270 # Make sure they actually typed something, and not just hit
1272 $attempt =~ s!(?:<p>(?: |\s)+</p>| )\s*?!!gm;
1273 $event = '' unless $attempt =~ /\S/;
1275 $req->{'prop_opt_preformatted'} = 0;
1277 # Old methods, left in for compatibility during code push
1278 $event =~ s!<lj-cut class="ljcut">!<lj-cut>!gi;
1280 $event =~ s!<lj-raw class="ljraw">!<lj-raw>!gi;
1283 $req->{"prop_used_rte"} = 0;
1286 $req->{'event'} = $event;
1288 ## see if an "other" mood they typed in has an equivalent moodid
1289 if ($POST->{'prop_current_mood'}) {
1290 if (my $id = LJ
::mood_id
($POST->{'prop_current_mood'})) {
1291 $req->{'prop_current_moodid'} = $id;
1293 $req->{'prop_current_moodid'} = '';
1297 # process site-specific options
1298 LJ
::run_hooks
('decode_entry_form', $POST, $req);
1303 # returns exactly what was passed to it normally. but in developer mode,
1304 # it includes a link to a page that automatically grants the needed priv.
1305 sub no_access_error
{
1306 my ($text, $priv, $privarg) = @_;
1307 if ($LJ::IS_DEV_SERVER
) {
1308 my $remote = LJ
::get_remote
();
1309 return "$text <b>(DEVMODE: <a href='/admin/priv/?devmode=1&user=$remote->{user}&priv=$priv&arg=$privarg'>Grant $priv\[$privarg\]</a>)</b>";
1315 # Data::Dumper for JavaScript
1319 return LJ
::JSON
->to_json($obj);
1321 return ($obj =~ /^[1-9]\d*$/) ?
$obj : '"' . LJ
::ejs
($obj) . '"';
1326 my %stat_cache = (); # key -> {lastcheck, modtime}
1328 my ($key, $now) = @_;
1329 if (my $ci = $stat_cache{$key}) {
1330 if ($ci->{lastcheck
} > $now - 10) {
1331 return $ci->{modtime
};
1335 my $file = "$LJ::HOME/htdocs/$key";
1336 my $mtime = (stat($file))[9];
1337 $stat_cache{$key} = { lastcheck
=> $now, modtime
=> $mtime };
1342 ## stc/0 is the empty file.
1343 ## its modtime is checked for all concatenated sources and it is updated
1344 ## for every release, so in the most cases this modtime
1345 ## as timestamp is used for "?v" param value.
1348 $now = time() unless defined $now;
1350 ## touch-ing it changes ?v= param for all included res.
1351 return _file_modtime
("stc/0", $now);
1354 sub stat_src_to_url
{
1356 my $mtime = _file_modtime
("/stc" . $url, time);
1357 return $LJ::STATPREFIX
. $url . "?v=" . $mtime;
1360 sub need_res_group
{
1361 my (@groupnames) = @_;
1362 foreach my $name (@groupnames){
1363 my $group = $LJ::RES_GROUP_DEPS
{$name};
1365 next if $LJ::REQ_GLOBAL
{__need_res_group
}->{$group}++;
1367 ## js, css, templates
1368 foreach my $resource_class (qw
/js css templates/){
1369 next unless exists $group->{$resource_class};
1370 foreach my $files ($group->{$resource_class}){
1371 foreach my $resource (@
$files){
1372 if (ref $resource eq 'ARRAY'){
1373 ## need_res expects $cond as a first argument,
1374 ## but for config files other order is preferable:
1375 ## source file name at first place then condition at second
1378 my ($file, $cond) = ref($resource->[0])
1379 ?
($resource->[1], $resource->[0])
1380 : ($resource->[0], $resource->[1]);
1381 LJ
::need_res
($cond, $file);
1383 LJ
::need_res
($resource);
1390 if (my $mls = $group->{ml
}){
1391 LJ
::need_string
(@
$mls);
1395 if (my $groups = $group->{groups
}){
1396 LJ
::need_res_group
($_) for @
$groups;
1401 sub need_journal_res
{
1403 paidrepost
.button
.title
1404 paidrepost
.button
.title
.owner
1405 paidrepost
.button
.title
.curr
1406 paidrepost
.button
.title
.delete
1407 paidrepost
.button
.title
.counter
1409 paidrepost
.button
.label
1410 paidrepost
.button
.label
.done
1412 entry
.reference
.label
.title
1413 entry
.reference
.label
.reposted
1416 LJ
::need_string
(@LJ::REPOST_ML
);
1418 LJ
::need_res
(@LJ::JOURNAL_RES_ALL
);
1421 ## Support for conditional file inclusion:
1422 ## e.g. LJ::need_res( {condition => 'IE'}, 'ie.css', 'myie.css') will result in
1423 ## <!--[if IE]><link rel="stylesheet" type="text/css" href="$statprefix/..." /><![endif]-->
1424 ## Support 'args' option. Example: LJ::need_res( { args => 'media="screen"' }, 'stc/ljtimes/iframe.css' );
1425 ## Results in: <link rel="stylesheet" type="text/css" href="http://stat.lj-3-32.bulyon.local/ljtimes/iframe.css?v=1285833891" media="screen"/>
1426 ## LJ::need_res( {clean_list => 1} ) will suppress ALL previous resources and do NOTHING more!
1427 ## LJ::need_res( {insert_head => 1}, 'my.css' ) insert my.css to the head of the list of sources.
1429 my $opts = (ref $_[0]) ?
shift : {};
1432 if ($opts->{clean_list
}) {
1433 %LJ::NEEDED_RES
= ();
1434 %LJ::NAMED_NEED_RES
= ();
1435 @LJ::NEEDED_RES
= ();
1436 @LJ::INCLUDE_TEMPLATE
= ();
1440 my $insert_head = $opts->{insert_head
} ?
1 : 0;
1443 foreach my $key (@keys) {
1445 my $resopts = $opts;
1446 if (ref $reskey eq 'ARRAY'){
1447 $reskey = $key->[1];
1448 $resopts = $key->[0];
1451 if ( $reskey =~ m!^templates/! ) {
1452 push @LJ::INCLUDE_TEMPLATE
, $reskey;
1456 die "Bogus reskey $reskey" unless $reskey =~ m!^(js|stc)/!;
1458 if ($opts->{'separate_list'}) {
1459 unless (exists $LJ::NEEDED_RES_SEPARATE
{$reskey}) {
1460 push @reskeys, $reskey;
1462 $LJ::NEEDED_RES_SEPARATE
{$reskey} = $resopts;
1464 unless (exists $LJ::NEEDED_RES
{$reskey}) {
1465 push @reskeys, $reskey;
1467 $LJ::NEEDED_RES
{$reskey} = $resopts;
1471 if ($opts->{'separate_list'}) {
1473 unshift @LJ::NEEDED_RES_SEPARATE
, @reskeys;
1475 push @LJ::NEEDED_RES_SEPARATE
, @reskeys;
1479 unshift @LJ::NEEDED_RES
, @reskeys;
1481 push @LJ::NEEDED_RES
, @reskeys;
1492 die "Bogus include type: $type"
1493 unless $type =~ m!^(?:js|css|js_link|css_link|html)$!;
1495 push @LJ::INCLUDE_RAW
=> [$type, $code];
1498 sub res_template_includes
{
1501 if (LJ
::is_enabled
('templates_from_stat')) {
1503 my $lang = LJ
::Lang
::current_language
();
1504 my $src = $LJ::IS_SSL?
$LJ::SSLROOT
: $LJ::STATPREFIX
;
1507 my $timestamp = int(time() / $LJ::TEMPLATES_UPDATE_TIME
);
1508 foreach my $extension ('.tmpl', '.jqtmpl') {
1511 my @files_list = map {
1514 my $lmtime = _file_modtime
($_, $time);
1515 $mtime = $lmtime if $lmtime > $mtime;
1516 s{^.*?templates/} {};
1519 -1 != index $_, $extension
1520 } @LJ::SITEWIDE_TEMPLATES
, @LJ::INCLUDE_TEMPLATE
;
1523 ?
join (join(',', @files_list),
1524 qq{<script type
="text/javascript" src
="$src},
1525 qq{?v=$mtime&tm=$timestamp;uselang=$lang"></script
>\n})
1529 foreach my $template (@LJ::SITEWIDE_TEMPLATES
, @LJ::INCLUDE_TEMPLATE
) {
1530 my $path = [split m{(?<!\\)/}, $template];
1531 my $file = pop @
$path;
1532 my ($type, $filter);
1534 shift @
$path if $path->[0] eq 'templates';
1536 $path = join '/', $LJ::TEMPLATE_BASE
, @
$path;
1537 my $fpath = join '/', $path, $file;
1539 -f
$fpath or warn 'Missing template '. $fpath and next;
1540 $loaded{lc $fpath}++ and next;
1543 m{\.jqtmpl$}i and do {
1544 $type = 'JQuery.tmpl';
1548 m{\.tmpl$}i and do {
1549 $type = 'HTML::Template';
1550 $filter = $LJ::TEMPLATE_FILTER
;
1556 my $data = LJ
::Response
::CachedTemplate
->new(
1560 translate
=> $LJ::TEMPLATE_TRANSLATION
,
1564 # Create template id
1565 my $key = $template;
1566 $key =~ s{(?<!\\)/} {-}g;
1567 $key =~ s{\.(?:jq)?tmpl$} {}g;
1569 # TODO: </script> in template can ruin your day
1570 if ( $LJ::IS_DEV_SERVER
) {
1571 $ret .= sprintf q{<script type="text/plain"
1577 data-translation="%s">},
1578 $key, $path, $file, $type, $filter, $LJ::TEMPLATE_TRANSLATION;
1579 $ret .= $data->raw_output();
1580 $ret .= '</script>';
1582 $ret .= sprintf q{<script type="text/plain" id="%s">}, $key;
1583 $ret .= $data->raw_output();
1584 $ret .= '</script>';
1587 # Let js know about template
1588 $ret .= sprintf q{<script>LJ.UI.registerTemplate('%s', '%s', '%s');</script>}, $key, $key, $LJ::TEMPLATE_TRANSLATION;
1596 my $opts = shift || {};
1597 my $only_needed = $opts->{only_needed}; # do not include defaults
1598 my $site_anyway = $opts->{site_anyway}; # Site anyway
1599 my $is_mobile = $opts->{is_mobile}; # m.livejournal.com
1601 my $no_sitewide_css = $opts->{no_sitewide_css}; # do not include @LJ::SITEWIDE_CSS
1603 # TODO: automatic dependencies from external map and/or content of files,
1604 # currently it's limited to dependencies on the order you call LJ::need_res();
1608 my %libs = (); ## pseudo files.
1609 my $do_concat = $LJ::IS_SSL ? $LJ::CONCAT_RES_SSL : $LJ::CONCAT_RES;
1612 # all conditions must be complete here
1613 # example: cyr/non-cyr flag changed at settings page
1614 unless ($only_needed) {
1615 LJ::run_hooks('sitewide_resources', {
1616 is_mobile => $is_mobile,
1617 no_sitewide_css => $no_sitewide_css,
1621 # use correct root and prefixes for SSL pages
1622 my ($siteroot, $imgprefix, $statprefix, $jsprefix, $wstatprefix);
1624 $siteroot = $LJ::SSLROOT;
1625 $imgprefix = $LJ::SSLIMGPREFIX;
1626 $statprefix = $LJ::SSLSTATPREFIX;
1627 $jsprefix = $LJ::SSLJSPREFIX;
1628 $wstatprefix = $LJ::SSLWSTATPREFIX;
1631 $siteroot = $LJ::SITEROOT;
1632 $imgprefix = $LJ::IMGPREFIX;
1633 $statprefix = $LJ::STATPREFIX;
1634 $jsprefix = $LJ::JSPREFIX;
1635 $wstatprefix = $LJ::WSTATPREFIX;
1638 # add jQuery.tmpl templates
1639 if ( $opts->{'only_tmpl'} ) {
1640 return res_template_includes;
1643 # include standard JS info
1644 unless ( $only_needed && !$site_anyway) {
1645 # find current journal
1646 my $journal_base = '';
1650 if (LJ::Request->is_inited) {
1651 my $journalid = LJ::Request->notes('journalid');
1653 $ju = LJ::load_userid($journalid) if $journalid;
1656 $journal_base = $ju->journal_base;
1657 $journal = $ju->{user};
1662 if (my $journalu = LJ::get_active_journal()) {
1663 %journal_info = $journalu->info_for_js;
1664 $journal_base ||= $journalu->journal_base;
1665 $journal ||= $journalu->username;
1668 my $remote = LJ::get_remote();
1669 my $hasremote = $remote ? 1 : 0;
1670 my $remote_is_suspended = $remote && $remote->is_suspended ? 1 : 0;
1674 $ctxpopup = 0 if $remote and not $remote->prop("opt_ctxpopup");
1675 $ctxpopup = 0 if LJ::Request->get_param('ctxpp') eq 'no';
1677 # poll for esn inbox updates?
1678 my $inbox_update_poll = $LJ::DISABLED{inbox_update_poll} ? 0 : 1;
1680 # are media embeds enabled?
1681 my $embeds_enabled = $LJ::DISABLED{embed_module} ? 0 : 1;
1684 my $esn_async = LJ::conf_test($LJ::DISABLED{esn_ajax}) ? 0 : 1;
1686 # remote is maintainer in current journal
1687 my $remote_is_maintainer = ($remote && $remote->can_manage($ju)) ? 1 : 0;
1689 my $default_copyright = $remote ? ($remote->prop("default_copyright") || 'P') : 'P';
1690 my $locale = LJ::Lang::current_language();
1691 $locale = $locale eq 'debug'? $locale : LJ::lang_to_locale($locale);
1693 my %comm_access = ();
1694 if ($ju && $ju->is_community && $remote && $remote->is_validated) {
1695 my @c_acc = LJ::get_comm_settings ($ju);
1696 $comm_access{'membership'} = $c_acc[0] if scalar @c_acc;
1699 my (undef, $country) = LJ::GeoLocation->ip_class ();
1700 my $ljentry = LJ::Request->notes('ljentry') || ''; # url
1702 imgprefix => "$imgprefix",
1703 siteroot => "$siteroot",
1704 statprefix => "$statprefix",
1705 jsonrpcprefix => "$LJ::JSON_RPC_PREFIX",
1706 logprefix => "$LJ::JSLOGPREFIX",
1707 picsUploadDomain => $LJ::PICS_UPLOAD_DOMAIN,
1708 currentJournalBase => "$journal_base",
1709 currentJournal => "$journal",
1710 currentEntry => $ljentry,
1711 has_remote => $hasremote,
1712 remote_can_track_threads => $remote && $remote->get_cap('track_thread'),
1713 remote_is_suspended => $remote_is_suspended,
1714 remote_is_maintainer => $remote_is_maintainer,
1715 remote_is_identity => $remote && $remote->is_identity,
1716 remote_is_sup => LJ::SUP->is_remote_sup()? 1 : 0,
1717 ctx_popup => $ctxpopup,
1718 inbox_update_poll => $inbox_update_poll,
1719 media_embed_enabled => $embeds_enabled,
1720 esn_async => $esn_async,
1721 server_time => int time(),
1722 templates_update_time => int $LJ::TEMPLATES_UPDATE_TIME || 600,
1723 remoteJournalBase => $remote && $remote->journal_base,
1724 remoteUser => $remote && $remote->user,
1725 remoteLocale => LJ::lang_to_locale( LJ::Lang::get_remote_lang() ),
1727 pics_production => LJ::is_enabled('pics_production'),
1728 v => stc_0_modtime($now),
1729 country => $country,
1730 counterprefix => "$LJ::LJCOUNTER_URI_BASE",
1733 $site{default_copyright} = $default_copyright if LJ::is_enabled('default_copyright', $remote);
1734 $site{is_dev_server} = 1 if $LJ::IS_DEV_SERVER;
1735 $site{inbox_unread_count} = $remote->notification_inbox->unread_count if $remote and LJ::is_enabled('inbox_unread_count_in_head');
1737 LJ::run_hooks('add_to_site_js', \%site) unless ($only_needed);
1739 LJ::need_var(D => \%LJ::JS_D) unless exists $LJ::JSVAR{'D'};
1741 my $site_params = LJ::js_dumper(\%site);
1744 return 'null' unless $_[0];
1746 $_[0] = LJ::JSON->to_json($_[0]);
1748 # LJSUP-12854: Fix escape for Site object
1749 $_[0] =~ s{(?<=</s)(?=cript)} {"+"}gi;
1754 my $journal_info_json = $to_json->(\%journal_info);
1755 my $jsml_out = $to_json->(\%LJ::JSML);
1756 my $jsvar_out = $to_json->(\%LJ::JSVAR);
1757 my $remote_info = $to_json->(get_remote_info());
1758 my $journal_info = $to_json->(get_journal_info());
1759 my $entry_info = $to_json->(get_entry_info());
1760 my $ljlive_info = $to_json->(get_ljlive_info());
1762 my $site_version = LJ::ejs($LJ::CURRENT_VERSION);
1765 <script type="text/javascript">
1766 Site = window.Site || {};
1767 Site.ml_text = $jsml_out;
1768 Site.page = $jsvar_out;
1769 Site.page.template = {};
1770 Site.page.ljlive = $ljlive_info;
1771 Site.timer = +(new Date());
1772 Site.remote = $remote_info;
1773 Site.journal = $journal_info;
1774 Site.entry = $entry_info;
1776 var p = $site_params, i;
1777 for (i in p) Site[i] = p[i];
1779 Site.current_journal = $journal_info_json;
1780 Site.version = '$site_version';
1784 } ## / unless $only_needed
1786 my $host = LJ::Request->header_in("Host");
1788 # foreign domain case
1789 if (not $host =~ /\.$LJ::DOMAIN(:\d+)?$/ and not $opts->{only_css}) {
1790 my $remote = LJ::get_remote();
1792 #first part of cross-domain auth
1795 my $hash_userid = sha1_base64($remote->{_session}->{userid} . $LJ::DOMAIN_JOURNALS_SECRET_KEY);
1797 <script type="text/javascript">
1798 lj_user = '$hash_userid';
1804 <script type="text/javascript">
1811 <script src="$siteroot/misc/get_auth_js.bml"></script>
1814 my $curl = LJ::Session::_current_url();
1815 $curl =~ m|^https?://(.+?)/|i;
1819 my $sign_time = time;
1820 my $curl_sign = LJ::run_hook('sign_set_domain_session_redirect' => $curl, $sign_time);
1822 $curl = LJ::eurl($curl);
1825 <script type="text/javascript">
1826 if( lj_user !== 0 && lj_master_user === 0 ) {
1827 window.location = "http://$domain/misc/clear_domain_session.bml?return=$curl";
1828 } else if ( lj_master_user !== 0 && lj_master_user !== lj_user ) {
1829 window.location = "${LJ::SITEROOT}/misc/get_domain_session.bml?return=$curl&sign=$curl_sign&t=$sign_time";
1834 my $minify_js_flag = LJ::Request->get_param("minify_js") eq 0;
1836 my %list; # type -> condition -> args -> [list of files];
1837 my %oldest; # type -> condition -> args -> $oldest
1839 my ($type, $what, $modtime, $opts) = @_;
1842 my $condition = $opts->{condition};
1843 $condition ||= ''; ## by default, no condtion is present
1845 my $args = $opts->{args};
1848 # in the concat-res case, we don't directly append the URL w/
1849 # the modtime, but rather do one global max modtime at the
1850 # end, which is done later in the tags function.
1851 unless ($do_concat){
1852 $what .= "?v=$modtime";
1853 $what .= "&minify_js=0" if $minify_js_flag and $type =~ /^js/;
1856 push @{$list{$type}{$condition}{$args} ||= []}, $what;
1857 $oldest{$type}{$condition}{$args} = $modtime if $modtime > $oldest{$type}{$condition}{$args};
1860 ## Replace sources with appropriate libraries
1861 unless ($only_needed){
1867 ## is the key part of library/package
1869 if ($library = ($LJ::JS_SOURCE_MAP_REV{$_} || $LJ::CSS_SOURCE_MAP_REV{$_})){
1870 $res = $libs{$library}++ ? '' : $library;
1876 my $mtime0 = stc_0_modtime($now);
1878 foreach my $key (@LJ::NEEDED_RES) {
1883 ## for libraries check mtime of all files
1885 if ($library_files = ($LJ::JS_SOURCES_MAP{$key} || $LJ::CSS_SOURCES_MAP{$key})){
1887 $libs{$library} = 1;
1888 foreach my $file (@$library_files){
1889 my $lmtime = _file_modtime($key, $now);
1890 $mtime = $lmtime if $lmtime > $mtime;
1894 $mtime = $mtime0 unless defined $mtime;
1898 if ($path =~ m!^js/(.+)!) {
1899 $add->("js$library", $1, $mtime, $LJ::NEEDED_RES{$key} || {});
1901 elsif ($path =~ /\.css$/ && $path =~ m!^(w?)stc/(.+)!) {
1902 $add->("${1}stccss$library", $2, $mtime, $LJ::NEEDED_RES{$key});
1904 elsif ($path =~ /\.js$/ && $path =~ m!^(w?)stc/(.+)!) {
1905 $add->("${1}stcjs", $2, $mtime, $LJ::NEEDED_RES{$key});
1909 foreach my $key (@LJ::NEEDED_RES_SEPARATE) {
1916 if ($path =~ m!^js/(.+)!) {
1917 $add->("js2$library", $1, $mtime, $LJ::NEEDED_RES_SEPARATE{$key} || {});
1919 elsif ($path =~ /\.css$/ && $path =~ m!^(w?)stc/(.+)!) {
1920 $add->("${1}stccss$library", $2, $mtime, $LJ::NEEDED_RES_SEPARATE{$key});
1922 elsif ($path =~ /\.js$/ && $path =~ m!^(w?)stc/(.+)!) {
1923 $add->("${1}stcjs", $2, $mtime, $LJ::NEEDED_RES_SEPARATE{$key});
1928 my ($type, $template) = @_;
1929 return unless $list{$type};
1930 return if $opts->{only_css}
1931 and $template =~ /^<script/;
1932 return if $opts->{only_js}
1933 and $template =~ /^<link/;
1935 my $minify_js = ($minify_js_flag and $template =~ /^<script/)
1936 ? '&minify_js=0' : "";
1938 foreach my $cond (sort {length($a) <=> length($b)} keys %{ $list{$type} }) {
1939 foreach my $args (sort {length($a) <=> length($b)} keys %{ $list{$type}{$cond} }) {
1940 my $list = $list{$type}{$cond}{$args};
1941 my $start = ($cond) ? "<!--[if $cond]>" : "";
1942 my $end = ($cond) ? "<![endif]-->\n" : "\n";
1945 my $csep = join(',', @$list);
1946 my $mtime = $oldest{$type}{$cond}{$args};
1948 ## shorten long (>20 symbols) links
1949 if (!$LJ::DISABLED{shorten_long_stat_links} and length ($csep) > 100 and not LJ::Request->get_param('fullstatlinks')) {
1950 my $short = LJ::URI::Shortener->uri_to_id($csep);
1951 $csep = "." . $short if $short;
1954 ## stc/0 is the empty file.
1955 ## touch-ing it changes ?v= param for all included res.
1956 my $mtime_base = stc_0_modtime($now);
1957 $mtime = $mtime_base if $mtime_base > $mtime;
1959 $csep .= "?v=" . $mtime;
1961 $csep .= $minify_js;
1963 my $inc = $template;
1964 $inc =~ s/__+/??$csep/;
1965 $inc =~ s/##/$args/;
1966 $ret .= $start . $inc . $end;
1969 foreach my $item (@$list) {
1970 my $inc = $template;
1971 $inc =~ s/__+/$item/;
1972 $inc =~ s/##/$args/;
1973 $ret .= $start . $inc . $end;
1981 ## To ensure CSS files are downloaded in parallel, always include external CSS before external JavaScript.
1982 ## (C) http://code.google.com/speed/page-speed/
1984 unless ($opts->{only_js}) {
1986 foreach my $library (@LJ::CSS_SOURCES_ORDER){ ## add libraries in strict order
1987 next unless $libs{$library};
1988 $tags->("stccss$library", "<link rel=\"stylesheet\" type=\"text/css\" href=\"$statprefix/___\" ##>");
1990 $tags->("stccss", "<link rel=\"stylesheet\" type=\"text/css\" href=\"$statprefix/___\" ##>");
1991 $tags->("wstccss", "<link rel=\"stylesheet\" type=\"text/css\" href=\"$wstatprefix/___\" ##>");
1994 unless ($opts->{only_css}) {
1996 foreach my $library (@LJ::JS_SOURCES_ORDER){ ## add libraries in strict order
1997 next unless $libs{$library};
1998 $tags->("js$library", "<script type=\"text/javascript\" src=\"$jsprefix/___\"></script>");
2001 $tags->("common_js", "<script type=\"text/javascript\" src=\"$jsprefix/___\"></script>");
2002 $tags->("js", "<script type=\"text/javascript\" src=\"$jsprefix/___\"></script>");
2003 $tags->("js2", "<script type=\"text/javascript\" src=\"$jsprefix/___\"></script>");
2004 $tags->("stcjs", "<script type=\"text/javascript\" src=\"$statprefix/___\"></script>");
2005 $tags->("wstcjs", "<script type=\"text/javascript\" src=\"$wstatprefix/___\"></script>");
2008 return $ret if $only_needed;
2011 foreach my $inc (@LJ::INCLUDE_RAW) {
2012 my ( $type, $code ) = @$inc;
2015 $ret .= qq|<script type="text/javascript">\r\n$code</script>\r\n| unless $opts->{only_css};
2017 elsif ($type eq 'css'){
2018 $ret .= qq|<style>\r\n$code</style>\n| unless $opts->{only_js};
2020 elsif ( $type eq 'js_link' ) {
2021 $ret .= qq{<script type="text/javascript" src="$code"></script>\r\n} unless $opts->{only_css};
2023 elsif ( $type eq 'css_link' ) {
2024 $ret .= qq{<link rel="stylesheet" type="text/css" href="$code" >} unless $opts->{only_js};
2026 elsif ( $type eq 'html' ) {
2027 $ret .= $code unless $opts->{only_css}; ## add raw html to js part
2035 sub get_remote_info {
2036 my $remote = LJ::get_remote();
2037 my $journal = LJ::get_active_journal();
2039 return unless $remote;
2043 alias => $remote->get_alias($journal),
2044 is_friend => LJ::JSON->to_boolean($remote->is_friend($remote)),
2045 is_subscribedon => LJ::JSON->to_boolean($remote->is_mysubscription($remote)),
2050 username => $remote->username,
2051 profile_url => $remote->profile_url,
2052 journal_url => $remote->journal_url,
2053 userhead_url => $remote->userhead_url,
2054 journal_title => $remote->journal_title,
2055 display_username => $remote->display_username,
2058 is_sup => LJ::JSON->to_boolean($remote->is_sup),
2059 is_paid => LJ::JSON->to_boolean($remote->is_paid),
2060 is_personal => LJ::JSON->to_boolean($remote->is_personal),
2061 is_identity => LJ::JSON->to_boolean($remote->is_identity),
2062 is_suspended => LJ::JSON->to_boolean($remote->is_suspended),
2063 is_community => LJ::JSON->to_boolean($remote->is_community),
2067 sub get_journal_info {
2068 my $remote = LJ::get_remote();
2069 my $journal = LJ::get_active_journal();
2071 return unless $journal;
2075 is_member => LJ::JSON->to_boolean($journal->is_member($remote)),
2076 is_friend => LJ::JSON->to_boolean($remote->is_friend($journal)),
2077 is_invite_sent => LJ::JSON->to_boolean($remote->is_invite_sent($journal)),
2078 is_subscribedon => LJ::JSON->to_boolean($remote->is_mysubscription($journal)),
2083 username => $journal->username,
2084 profile_url => $journal->profile_url,
2085 journal_url => $journal->journal_url,
2086 userhead_url => $journal->userhead_url,
2087 journal_title => $journal->journal_title,
2088 display_username => $journal->display_username,
2091 is_paid => LJ::JSON->to_boolean($journal->is_paid),
2092 is_personal => LJ::JSON->to_boolean($journal->is_personal),
2093 is_identity => LJ::JSON->to_boolean($journal->is_identity),
2094 is_suspended => LJ::JSON->to_boolean($journal->is_suspended),
2095 is_community => LJ::JSON->to_boolean($journal->is_community),
2099 sub get_entry_info {
2100 my $entry = LJ::Entry->new_from_url(LJ::Request->current_page_url());
2101 return unless $entry && $entry->valid;
2103 my $journal = $entry->journal;
2104 return unless $journal;
2106 my $poster = $entry->poster;
2107 return unless $poster;
2110 ditemid => $entry->ditemid,
2111 title => $entry->subject_raw,
2112 poster => $poster->user,
2113 journal => $journal->user,
2117 # Discovery times branding
2118 sub get_ljlive_info {
2121 LJ::run_hooks('ljtimes_rebranding' => {
2122 'location' => 'reskining_html',
2123 'bodyref' => \$bodyref,
2127 ($bodyref ? (branding_template => $bodyref) : ()),
2128 is_enabled => LJ::JSON->to_boolean(LJ::Discovery::Times->is_enabled),
2132 # Returns HTML of a dynamic tag could given passed in data
2133 # Requires hash-ref of tag => { url => url, value => value }
2135 my ($tags, $opts) = @_;
2137 # find sizes of tags, sorted
2138 my @sizes = sort { $a <=> $b } map { $tags->{$_}->{'value'} } keys %$tags;
2140 # remove duplicates:
2141 my %sizes = map { $_, 1 } @sizes;
2142 @sizes = sort { $a <=> $b } keys %sizes;
2144 my @tag_names = sort keys %$tags;
2146 my $percentile = sub {
2148 my $total = scalar @sizes;
2149 for (my $i = 0; $i < $total; $i++) {
2150 next if $n > $sizes[$i];
2155 my $base_font_size = 8;
2156 my $font_size_range = $opts->{font_size_range} || 25;
2157 my $ret .= "<div id='tagcloud' class='tagcloud'>";
2159 foreach my $tag (@tag_names) {
2160 my $tagurl = $tags->{$tag}->{'url'};
2161 my $ct = $tags->{$tag}->{'value'};
2162 my $pt = int($base_font_size + $percentile->($ct) * $font_size_range);
2164 $ret .= "id='taglink_$tag' " unless $opts->{ignore_ids};
2165 $ret .= "href='" . LJ::ehtml($tagurl) . "' style='font-size: ${pt}pt;'><span>";
2166 $ret .= LJ::ehtml($tag) . "</span></a>\n";
2168 # build hash of tagname => final point size for refresh
2169 $tagdata{$tag} = $pt;
2176 sub get_next_ad_id {
2177 return ++$LJ::REQ_GLOBAL{'curr_ad_id'};
2181 ## Function LJ::check_page_ad_block. Return answer (true/false) to question:
2182 ## Should we show ad of this type on this page.
2183 ## Args: uri of the page and orient of the ad block (e.g. 'App-Confirm')
2185 sub check_page_ad_block {
2189 # The AD_MAPPING hash may contain code refs
2190 # This allows us to choose an ad based on some logic
2191 # Example: If LJ::did_post() show 'App-Confirm' type ad
2192 my $ad_mapping = LJ::run_hook('get_ad_uri_mapping', $uri) ||
2193 LJ::conf_test($LJ::AD_MAPPING{$uri});
2195 return 1 if $ad_mapping eq $orient;
2196 return 1 if ref($ad_mapping) eq 'HASH' && $ad_mapping->{$orient};
2200 # returns a hash with keys "layout" and "theme"
2201 # "theme" is empty for S1 users
2202 sub get_style_for_ads {
2209 # Values for custom layers, default themes, and S1 styles
2210 my $custom_layout = "custom_layout";
2211 my $custom_theme = "custom_theme";
2212 my $default_theme = "default_theme";
2213 my $s1_prefix = "s1_";
2215 if ($u->prop('stylesys') == 2) {
2216 my %style = LJ::S2::get_style($u);
2217 my $public = LJ::S2::get_public_layers();
2220 my $layout = $public->{$style{layout}}->{uniq}; # e.g. generator/layout
2221 $layout =~ s/\/\w+$//;
2224 # if the theme id == 0, then we have no theme for this layout (i.e. default theme)
2226 if ($style{theme} == 0) {
2227 $theme = $default_theme;
2229 $theme = $public->{$style{theme}}->{uniq}; # e.g. generator/mintchoc
2230 $theme =~ s/^\w+\///;
2233 $ret{layout} = $layout ? $layout : $custom_layout;
2234 $ret{theme} = $theme ? $theme : $custom_theme;
2236 my $view = LJ::Request->notes->{view};
2237 $view = "lastn" if $view eq "";
2239 if ($view =~ /^(?:friends|day|calendar|lastn)$/) {
2240 my $pubstyles = LJ::S1::get_public_styles();
2241 my $styleid = $u->prop("s1_${view}_style");
2244 if ($pubstyles->{$styleid}) {
2245 $layout = $pubstyles->{$styleid}->{styledes}; # e.g. Clean and Simple
2248 $layout = lc $layout;
2249 $layout = $s1_prefix . $layout;
2252 $ret{layout} = $layout ? $layout : $s1_prefix . $custom_layout;
2259 sub get_search_term {
2261 my $search_arg = shift;
2263 my %search_pages = (
2264 '/interests.bml' => 1,
2265 '/directory.bml' => 1,
2266 '/multisearch.bml' => 1,
2269 return "" unless $search_pages{$uri};
2272 my $args = LJ::Request->args;
2273 if ($uri eq '/interests.bml') {
2274 if ($args =~ /int=([^&]+)/) {
2277 } elsif ($uri eq '/directory.bml') {
2278 if ($args =~ /int_like=([^&]+)/) {
2281 } elsif ($uri eq '/multisearch.bml') {
2282 $term = $search_arg;
2285 # change +'s to spaces
2292 # this returns ad html given a search string
2296 return '' if LJ::conf_test($LJ::DISABLED{content_ads});
2298 return '' unless $LJ::USE_JS_ADCALL_FOR_SEARCH;
2300 my $remote = LJ::get_remote();
2302 return '' unless LJ::run_hook('should_show_ad', {
2308 return '' unless LJ::run_hook('should_show_search_ad');
2310 my $query = delete $opts{query} or croak "No search query specified in call to search_ads";
2311 my $count = int(delete $opts{count} || 1);
2312 my $adcount = int(delete $opts{adcount} || 3);
2314 my $adid = get_next_ad_id();
2315 my $divid = "ad_$adid";
2317 my @divids = map { "ad_$_" } (1 .. $count);
2320 u => join(',', map { $adcount } @divids), # how many ads to show in each
2323 id => join(',', @divids),
2325 add => 'lj_content_ad',
2326 remove => 'lj_inactive_ad',
2330 $adcall{user} = $remote->id;
2333 my $adparams = LJ::encode_url_string(\%adcall,
2335 sort { length $adcall{$a} <=> length $adcall{$b} }
2336 grep { length $adcall{$_} }
2341 # allow 24 bytes for escaping overhead
2342 $adparams = substr($adparams, 0, 1_000);
2344 my $url = $LJ::ADSERVER . '/google/?' . $adparams;
2349 if (++$LJ::REQ_GLOBAL{'curr_search_ad_id'} == $count) {
2350 $adcall .= qq { <script charset="utf-8" id="ad${adid}s" src="$url"></script>\n };
2351 $adcall .= qq { <script language="javascript" src="http://www.google.com/afsonline/show_afs_ads.js"></script> };
2355 <div class="lj_inactive_ad" id="$divid" style="clear: left;">
2358 <div class='lj_inactive_ad clear'> </div>
2365 LJ::run_hook('ADV_get_ad_html', @_);
2368 sub should_show_ad {
2369 LJ::run_hook('ADV_should_show_ad', @_);
2372 # modifies list of interests (appends tags of sponsored questions to the list)
2373 # sponsored question may be taken
2374 # 1. from argument of function: $opts = { extra => {qotd => ...} },
2375 # 2. from URL args of /update.bml page (/update.bml?qotd=123)
2376 # 3. from first displayed entry on the page
2377 sub modify_interests_for_adcall {
2382 if (ref $opts->{extra} && $opts->{extra}->{qotd}) {
2383 $qotd = $opts->{extra}->{qotd};
2384 } elsif (LJ::Request->is_inited && LJ::Request->notes('codepath') eq 'bml.update' && $BMLCodeBlock::GET{qotd}) {
2385 $qotd = $BMLCodeBlock::GET{qotd};
2386 } elsif (@LJ::SUP_LJ_ENTRY_REQ) {
2387 my ($journalid, $posterid, $ditemid) = @{ $LJ::SUP_LJ_ENTRY_REQ[0] };
2388 my $entry = LJ::Entry->new(LJ::load_userid($journalid), ditemid => $ditemid);
2389 if ($entry && $entry->prop("qotdid")) {
2390 $qotd = $entry->prop("qotdid");
2395 $qotd = LJ::QotD->get_single_question($qotd) unless ref $qotd;
2396 my $tags = LJ::QotD->remove_default_tags($qotd->{tags});
2397 if ($tags && $qotd->{is_special} eq "Y") {
2398 unshift @$list, $tags;
2403 # this function will filter out blocked interests, as well filter out interests which
2405 sub interests_for_adcall {
2409 # base ad call is 300-400 bytes, we'll allow interests to be around 600
2410 # which is unlikely to go over IE's 1k URL limit.
2411 my $max_len = $opts{max_len} || 600;
2415 my @interest_list = $u ? $u->notable_interests(100) : ();
2417 modify_interests_for_adcall(\%opts, \@interest_list);
2422 # not a blocked interest
2423 ! defined $LJ::AD_BLOCKED_INTERESTS{$_} &&
2425 # and we've not already got over 768 bytes of interests
2426 # -- +1 is for comma
2427 ($int_len += length($_) + 1) <= $max_len;
2433 # for use when calling an ad from BML directly
2437 # can specify whether the wrapper div on the ad is used or not
2438 my $use_wrapper = defined $opts{use_wrapper} ? $opts{use_wrapper} : 1;
2440 my $ret = LJ::ads(%opts);
2443 if ($ret =~ /"ljad ljad(.+?)"/i) {
2444 # Add a badge ad above all skyscrapers
2445 # First, try to print a badge ad in journal context (e.g. S1 comment pages)
2446 # Then, if it doesn't print, print it in user context (e.g. normal app pages)
2447 if ($1 eq "skyscraper") {
2448 $extra = LJ::ads(type => $opts{'type'},
2449 orient => 'Journal-Badge',
2450 user => $opts{'user'},
2451 search_arg => $opts{'search_arg'},
2453 $extra = LJ::ads(type => $opts{'type'},
2454 orient => 'App-Extra',
2455 user => $opts{'user'},
2456 search_arg => $opts{'search_arg'},
2460 $ret = $extra . $ret
2463 my $pagetype = $opts{orient};
2464 $pagetype =~ s/^BML-//;
2465 $pagetype = lc $pagetype;
2467 $ret = $opts{below_ad} ? "$ret<br />$opts{below_ad}" : $ret;
2468 $ret = $ret && $use_wrapper ? "<div class='ljadwrapper-$pagetype'>$ret</div>" : $ret;
2476 return LJ::ControlStrip->render($opts{user});
2479 sub control_strip_js_inject {
2482 LJ::ControlStrip->need_res(%opts);
2485 sub journal_js_inject {
2486 LJ::need_journal_res();
2491 js/jquery/jquery.lj.confirmbubble.js
2492 js/jquery/jquery.lj.ljcut.js
2495 LJ
::run_hooks
('extra_journal_js');
2498 # For the Rich Text Editor
2499 # Set JS variables for use by the RTE
2504 # The JS var canmakepoll is used by fckplugin.js to change the behaviour
2505 # of the poll button in the RTE.
2506 # Also remove any RTE buttons that have been set to disabled.
2507 my $canmakepoll = "true";
2508 $canmakepoll = "false" if ($remote && !LJ
::get_cap
($remote, 'makepoll'));
2509 $ret .= "<script type='text/javascript'>\n";
2510 $ret .= " var RTEdisabled = new Array();\n";
2511 LJ
::need_var
(makepoll
=> $canmakepoll eq 'true'?
1 : 0);
2512 my $rte_disabled = $LJ::DISABLED
{rte_buttons
} || {};
2513 foreach my $key (keys %$rte_disabled) {
2514 $ret .= " RTEdisabled['$key'] = true;" if $rte_disabled->{$key};
2517 var canmakepoll
= $canmakepoll;
2519 function removeDisabled
(ToolbarSet
) {
2520 for (var i
=0; i
<ToolbarSet
.length; i
++) {
2521 for (var j
=0; j
<ToolbarSet
[i
].length; j
++) {
2522 if (RTEdisabled
[ToolbarSet
[i
][j
]] == true
) ToolbarSet
[i
].splice(j
,1);
2531 # returns a placeholder link
2532 sub placeholder_link
{
2535 my $placeholder_html = LJ
::ejs_all
(delete $opts{placeholder_html
} || '');
2536 my $width = delete $opts{width
} || 100;
2537 my $height = delete $opts{height
} || 100;
2538 my $link = delete $opts{link} || '';
2539 my $img = delete $opts{img
} || "$LJ::IMGPREFIX/videoplaceholder.png?v=8209";
2545 <a href
="$link" class="b-mediaplaceholder b-mediaplaceholder-video } . ( $opts{remove_video_sizes} ? '" ' : ' b
-mediaplaceholder
-good
" style="width
:' . $width . 'px
;height
:' . $height . 'px
;"' ) . ( $width ? qq~ data-width="$width"~ : '' ) . ( $height ? qq~ data-height="$height"~: '' ) . qq{ onclick="return LiveJournal
.placeholderClick
(this
, '$placeholder_html')">
2546 <span class="b
-mediaplaceholder
-outer
">
2547 <span class="b
-mediaplaceholder
-inner
">
2548 <i class="b
-mediaplaceholder
-pic
"></i>
2549 <span class="b
-mediaplaceholder
-label b
-mediaplaceholder
-view
">} . ($opts{no_encode} ? Encode::decode_utf8(LJ::Lang::ml("mediaplaceholder
.viewvideo
")) : Encode::encode_utf8(Encode::decode_utf8(LJ::Lang::ml("mediaplaceholder
.viewvideo
")))) . qq{</span>
2550 <span class="b
-mediaplaceholder
-label b
-mediaplaceholder
-loading
">} . ($opts{no_encode} ? Encode::decode_utf8(LJ::Lang::ml("mediaplaceholder
.loading
")) : Encode::encode_utf8(Encode::decode_utf8(LJ::Lang::ml("mediaplaceholder
.loading
")))) . qq{</span>
2557 # Returns replacement for lj-replace tags
2562 # Return hook if hook output not undef
2563 if (LJ
::are_hooks
("lj-replace_$key")) {
2564 my $replace = LJ
::run_hook
("lj-replace_$key");
2565 return $replace if defined $replace;
2568 # Return value of coderef if key defined
2569 my %valid_keys = ( 'first_post' => \
&lj_replace_first_post
);
2571 if (my $cb = $valid_keys{$key}) {
2572 die "$cb is not a valid coderef" unless ref $cb eq 'CODE';
2573 return $cb->($attr);
2579 # Replace for lj-replace name="first_post"
2580 sub lj_replace_first_post
{
2581 return unless LJ
::is_web_context
();
2582 return BML
::ml
('web.lj-replace.first_post', {
2583 'update_link' => "href='$LJ::SITEROOT/update.bml'",
2587 # this returns the right max length for a VARCHAR(255) database
2588 # column. but in HTML, the maxlength is characters, not bytes, so we
2589 # have to assume 3-byte chars and return 80 instead of 255. (80*3 ==
2590 # 240, approximately 255). However, we special-case Russian where
2591 # they often need just a little bit more, and make that 100. because
2592 # their bytes are only 2, so 100 * 2 == 200. as long as russians
2593 # don't enter, say, 100 characters of japanese... but then it'd get
2594 # truncated or throw an error. we'll risk that and give them 20 more
2596 sub std_max_length
{
2597 my $lang = eval { BML
::get_language
() };
2598 return 80 if !$lang || $lang =~ /^en/;
2599 return 100 if $lang =~ /\b(hy|az|be|et|ka|ky|kk|lt|lv|mo|ru|tg|tk|uk|uz)\b/i;
2603 # Common challenge/response JavaScript, needed by both login pages and comment pages alike.
2604 # Forms that use this should onclick='return sendForm()' in the submit button.
2605 # Returns true to let the submit continue.
2606 $LJ::COMMON_CODE
{'chalresp_js'} = qq{
2607 <script language
="JavaScript" type
="text/javascript">
2609 function sendForm
(formid
, checkuser
)
2611 if (formid
== null
) formid
= 'login';
2612 // 'checkuser' is the element id name of the username textfield
.
2613 // only
use it
if you care to verify a username
exists before hashing
.
2615 if (! document
.getElementById
) return true
;
2616 var loginform
= document
.getElementById
(formid
);
2617 if (! loginform
) return true
;
2618 if(document
.getElementById
('prop_current_location')){
2619 if(document
.getElementById
('prop_current_location').value
=='detecting...') document
.getElementById
('prop_current_location').value
='';
2621 // Avoid accessing the password field
if there is
no username
.
2622 // This works around Opera
< 7 complaints
when commenting
.
2624 var username
= null
;
2625 for (var i
= 0; username
== null
&& i
< loginform
.elements
.length; i
++) {
2626 if (loginform
.elements
[i
].id
== checkuser
) username
= loginform
.elements
[i
];
2628 if (username
!= null
&& username
.value
== "") return true
;
2631 if (! loginform
.password
|| ! loginform
.login_chal
|| ! loginform
.login_response
) return true
;
2632 var pass
= loginform
.password
.value
;
2633 var chal
= loginform
.login_chal
.value
;
2634 var res
= MD5
(chal
+ MD5
(pass
));
2635 loginform
.login_response
.value
= res
;
2636 loginform
.password
.value
= ""; // dont
send clear
-text password
!
2643 # Common JavaScript function for auto-checking radio buttons on form
2644 # input field data changes
2645 $LJ::COMMON_CODE
{'autoradio_check'} = q{
2646 <script language="JavaScript" type="text/javascript">
2648 /* If radioid exists, check the radio button. */
2649 function checkRadioButton(radioid) {
2650 if (!document.getElementById) return;
2651 var radio = document.getElementById(radioid);
2653 radio.checked = true;
2659 sub initial_body_html
{
2660 my $after_body_open = '';
2661 LJ
::run_hooks
('insert_html_after_body_open', \
$after_body_open);
2662 return $after_body_open;
2665 # returns HTML which should appear before </body>
2666 sub final_body_html
{
2667 my $before_body_close = "";
2668 LJ
::run_hooks
('insert_html_before_body_close', \
$before_body_close);
2670 if (LJ
::Request
->notes('codepath') eq "bml.talkread" || LJ
::Request
->notes('codepath') eq "bml.talkpost") {
2671 my $journalu = LJ
::load_userid
(LJ
::Request
->notes('journalid'));
2672 unless (LJ
::Request
->notes('bml_use_scheme') eq 'lynx') {
2673 my $graphicpreviews_obj = LJ
::graphicpreviews_obj
();
2674 $before_body_close .= $graphicpreviews_obj->render($journalu);
2678 return $before_body_close;
2681 # return a unique per pageview string based on the remote's unique cookie
2682 sub pageview_unique_string
{
2683 my $cached_uniq = $LJ::REQ_GLOBAL
{pageview_unique_string
};
2684 return $cached_uniq if $cached_uniq;
2686 my $uniq = LJ
::UniqCookie
->current_uniq . time() . LJ
::rand_chars
(8);
2687 $uniq = Digest
::SHA1
::sha1_hex
($uniq);
2689 $LJ::REQ_GLOBAL
{pageview_unique_string
} = $uniq;
2694 # name: LJ::site_schemes
2696 # des: Returns a list of available BML schemes.
2701 my @schemes = @LJ::SCHEMES
;
2702 LJ
::run_hooks
('modify_scheme_list', \
@schemes);
2703 @schemes = grep { !$_->{disabled
} } @schemes;
2707 # returns a random value between 0 and $num_choices-1 for a particular uniq
2708 # if no uniq available, just returns a random value between 0 and $num_choices-1
2709 sub ab_testing_value
{
2712 return $LJ::DEBUG
{ab_testing_value
} if defined $LJ::DEBUG
{ab_testing_value
};
2714 my $num_choices = $opts{num_choices
} || 2;
2715 my $uniq = LJ
::UniqCookie
->current_uniq;
2719 $val = unpack("I", $uniq);
2720 $val %= $num_choices;
2722 $val = int(rand($num_choices));
2728 # sets up appropriate js for journals that need a special statusvis message at the top
2729 # returns some js that must be added onto the journal page's head
2730 sub statusvis_message_js
{
2733 return "" unless $u;
2735 my $statusvis = $u->statusvis;
2736 return "" unless $statusvis =~ /^[LMO]$/;
2738 my $statusvis_full = "";
2739 $statusvis_full = "locked" if $statusvis eq "L";
2740 $statusvis_full = "memorial" if $statusvis eq "M";
2741 $statusvis_full = "readonly" if $statusvis eq "O";
2743 LJ
::need_res
("js/statusvis_message.js");
2745 LJ
::need_var
(StatusvisMessage
=> LJ
::Lang
::ml
("statusvis_message.$statusvis_full"));
2748 sub needlogin_redirect_url
{
2749 my $uri = LJ
::Request
->uri;
2750 if (my $qs = LJ
::Request
->args) {
2753 $uri = LJ
::eurl
($uri);
2755 return "$LJ::SITEROOT/login.bml?returnto=$uri";
2758 sub needlogin_redirect
{
2759 return LJ
::Request
->redirect( LJ
::needlogin_redirect_url
() );
2762 sub get_body_class_for_service_pages
{
2766 push @classes, @
{ $opts{'classes'} } if $opts{'classes'};
2767 push @classes, (LJ
::get_remote
()) ?
'logged-in' : 'logged-out';
2768 push @classes, 'p-ssl' if $LJ::IS_SSL
;
2770 my $uri = LJ
::Request
->uri;
2771 my $host = LJ
::Request
->header_in("Host");
2772 if ($uri =~ m!^/index\.bml$!) {
2773 push @classes, "index-page";
2774 } elsif ($uri =~ m{^/stats/latest\.bml$}) {
2775 push @classes, "p-lenta";
2776 } elsif ($uri =~ m!^/shop(/.*)?$!) {
2777 push @classes, "shop-page";
2778 } elsif ($uri =~ m!^/pics(/.*)?$!) {
2779 if (LJ
::_is_pics_branding_active
()) {
2780 my ($user) = $host =~ /([\w\-]{1,15})\.\Q$LJ::DOMAIN\E$/;
2781 $user = LJ
::get_remote
() || LJ
::load_user
($user);
2782 if ($user && LJ
::Pics
::Album
->list( 'userid' => $user->userid )) {
2784 push @classes, "b-foto-branding-view";
2787 push @classes, "b-foto-branding-promo";
2790 ## branding is not active
2791 push @classes, "framework-page";
2793 } elsif ($uri =~ m!^/browse(/.*)?$!) {
2794 push @classes, "catalogue-page";
2796 $uri =~ m!^/games(/.*)?$!
2797 || $host eq "$LJ::USERAPPS_SUBDOMAIN.$LJ::DOMAIN"
2798 || $uri =~ m!^/adv(/.*)?$!
2800 push @classes, 'framework-page';
2801 } elsif ($uri =~ m
|^/friendstimes
|
2802 or ($host =~ m!^(\w+)\.\Q$LJ::USER_DOMAIN\E$!
2803 and $LJ::IS_USER_DOMAIN
->{$1}
2804 and $uri =~ m!/([\w-]+)/friendstimes/?!
2807 push @classes, "p-friendstimes";
2808 } elsif (LJ
::Request
->notes ("homepage_v2")) {
2809 push @classes, "p-home";
2811 if ($uri =~ m!^/(?:update|editjournal)\.bml!) {
2812 push @classes, "b-foto-branding"
2813 if LJ
::_is_pics_branding_active
();
2816 LJ
::run_hooks
( 'get_body_class_for_service_pages', \
@classes );
2818 return join(" ", @classes);
2821 # Add some javascript language strings
2825 for my $item (@strings) {
2826 # When comes as a hash ref, should be treated as name => value
2827 if(ref $item eq 'HASH') {
2828 for my $key (keys %$item) {
2829 $LJ::JSML
{$key} = $item->{$key};
2831 # When handling array ref, name the ml by the value of the second element
2832 } elsif(ref $item eq 'ARRAY') {
2833 $LJ::JSML
{$$item[1]} = LJ
::Lang
::ml
($$item[0]);
2834 # If scalar - use the ml named this way
2836 $LJ::JSML
{$item} = LJ
::Lang
::ml
($item);
2841 # Add some javascript variables
2845 # Our arguments are hash ref
2846 if (@_ == 1 and ref $_[0] and ref $_[0] eq 'HASH') {
2848 # List of key-value pairs otherwise
2850 while (my ($k, $v) = splice @_, 0, 2) {
2855 while (my ($k, $v) = each %vars) {
2856 warn 'JS Variable override: '. $k
2857 if $LJ::IS_DEV_SERVER
and exists $LJ::JSVAR
{$k};
2859 $LJ::JSVAR
{$k} = $v;
2863 sub set_remote_language
{
2866 my $l = LJ
::Lang
::get_lang
($lang);
2867 my $remote = LJ
::get_remote
();
2871 my $cval = $l->{'lncode'} . '/' . time();
2873 # if logged in, change userprop and make cookie expiration
2874 # the same as their login expiration
2876 $remote->set_prop( 'browselang' => $l->{lncode
} );
2878 if ( $remote->{'_session'}->{'exptype'} eq 'long' ) {
2879 $exptime = $remote->{'_session'}->{'timeexpire'};
2884 LJ
::Request
->set_cookie( 'langpref' => $cval, 'expires' => $exptime );
2886 # set language through BML so it will apply immediately
2887 BML
::set_language
( $l->{'lncode'} );
2894 return undef unless $url;
2895 return undef unless $LJ::PAGE_PRIVILEGES
{$url};
2896 my $priv = $LJ::PAGE_PRIVILEGES
{$url}{'priv'};
2897 my $arg = $LJ::PAGE_PRIVILEGES
{$url}{'arg'};
2898 return "$priv:$arg";
2901 # http://ogp.me/, https://dev.twitter.com/docs/cards
2904 return '' unless $meta;
2906 # https://dev.twitter.com/docs/cards/app-installs-and-deep-linking
2907 (my $iosScheme .= $meta->{'url'}) =~ s/^(http)/lj/;
2910 'og:title' => $meta->{'title'} || '(no title)',
2911 'og:description' => $meta->{'description'} || '',
2912 'og:image' => $meta->{'image'},
2913 'og:type' => 'website',
2914 'og:url' => $meta->{'url'} || $LJ::SITEROOT
,
2915 'twitter:card' => 'summary',
2916 'twitter:site' => '@livejournal',
2918 'twitter:app:name:iphone' => 'LiveJournal',
2919 "twitter:app:id:iphone" => '383091547',
2920 "twitter:app:url:iphone" => $iosScheme,
2921 "twitter:app:name:ipad" => 'LiveJournal',
2922 "twitter:app:id:ipad" => '383091547',
2923 "twitter:app:url:ipad" => $iosScheme
2927 foreach my $k ( sort keys %tags ) {
2928 my $property_ehtml = LJ
::ehtml
($k);
2929 my $content_ehtml = LJ
::ehtml
( $tags{$k} );
2931 qq{<meta property
="$property_ehtml" content
="$content_ehtml" />};