[#3029] allow listing of multiple accounts for a service on user's profile (#3180)

* update bin/dumpsql.pl

The schematables database table was dropped in b3bf367e8d, so
the section of the dumpsql.pl script that used to regenerate
the base-data.sql file doesn't do anything any more.

* new global table profile_services

maps new database list of profile services to legacy userprops
and incorporates additional data from the external_services
function in DW/Logic/ProfilePage.pm and from /manage/profile

* update DW/Logic/ProfilePage.pm to use data from profile_services table

* move data list into new module DW::External::ProfileServices

* Do I remember how to create a clustered table? Let's find out.

* new user method load_profile_accts

* new user method save_profile_accts

* allow adding more than one of the same type of site

* move text_trim up in the stack, use it for safer string truncation

* use confess if there are database errors

* bypass memcache when preparing to save

* rewrite the entire database layer nbd
This commit is contained in:
Jen 2023-09-30 19:56:09 -05:00 committed by GitHub
parent 0ea48b3557
commit 166efb8bb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 446 additions and 600 deletions

View File

@ -21,125 +21,6 @@ BEGIN {
my $dbh = LJ::get_db_writer();
sub header_text {
return <<"HEADER";
# This file is automatically generated from MySQL by \$LJHOME/bin/dumpsql.pl
# Don't submit a diff against a hand-modified file - dump and diff instead.
HEADER
}
# what tables don't we want to export the auto_increment columns from
# because they already have their own unique string, which is what matters
my %skip_auto = (
"priv_list" => "privcode",
"supportcat" => "catkey",
"ratelist" => "name",
);
# get tables to export
my %tables = ();
my $sth = $dbh->prepare( "SELECT tablename, redist_mode, redist_where "
. "FROM schematables WHERE redist_mode NOT IN ('off')" );
$sth->execute;
while ( my ( $table, $mode, $where ) = $sth->fetchrow_array ) {
$tables{$table}->{'mode'} = $mode;
$tables{$table}->{'where'} = $where;
}
my %output; # {general|local} -> [ [ $alphasortkey, $SQL ]+ ]
# dump each table.
foreach my $table ( sort keys %tables ) {
next if $table =~ /^(user|talk|log)proplist$/;
my $where;
if ( $tables{$table}->{'where'} ) {
$where = "WHERE $tables{$table}->{'where'}";
}
my $sth = $dbh->prepare("DESCRIBE $table");
$sth->execute;
my @cols = ();
my $skip_auto = 0;
while ( my $c = $sth->fetchrow_hashref ) {
if ( $c->{'Extra'} =~ /auto_increment/ && $skip_auto{$table} ) {
$skip_auto = 1;
}
else {
push @cols, $c;
}
}
# DESCRIBE table can be different between developers
@cols = sort { $a->{'Field'} cmp $b->{'Field'} } @cols;
my $cols = join( ", ", map { $_->{'Field'} } @cols );
my $sth = $dbh->prepare("SELECT $cols FROM $table $where");
$sth->execute;
my $sql;
while ( my @r = $sth->fetchrow_array ) {
my %vals;
my $i = 0;
foreach ( map { $_->{'Field'} } @cols ) {
$vals{$_} = $r[ $i++ ];
}
my $scope = "general";
$scope = "local" if ( defined $vals{'scope'}
&& $vals{'scope'} eq "local" );
my $verb = "INSERT IGNORE";
$verb = "REPLACE" if ( $tables{$table}->{'mode'} eq "replace"
&& !$skip_auto );
$sql = "$verb INTO $table ";
$sql .= "($cols) ";
$sql .= "VALUES (" . join( ", ", map { db_quote($_) } @r ) . ");\n";
my $uniqc = $skip_auto{$table};
my $skey = $uniqc ? $vals{$uniqc} : $sql;
push @{ $output{$scope} }, [ "$table.$skey.1", $sql ];
if ($skip_auto) {
# for all the *proplist tables, there might be new descriptions
# or columns, but we can't do a REPLACE, because that'd mess
# with their auto_increment ids, so we do insert ignore + update
my $where = "$uniqc=" . db_quote( $vals{$uniqc} );
delete $vals{$uniqc};
$sql = "UPDATE $table SET ";
$sql .= join( ",", map { "$_=" . db_quote( $vals{$_} ) } sort keys %vals );
$sql .= " WHERE $where;\n";
push @{ $output{$scope} }, [ "$table.$skey.2", $sql ];
}
}
}
# don't use $dbh->quote because it's changed between versions
# and developers sending patches can't generate concise patches
# it used to not quote " in a single quoted string, but later it does.
# so we'll implement the new way here.
sub db_quote {
my $s = shift;
return "NULL" unless defined $s;
$s =~ s/\\/\\\\/g;
$s =~ s/\"/\\\"/g;
$s =~ s/\'/\\\'/g;
$s =~ s/\n/\\n/g;
$s =~ s/\r/\\r/g;
return "'$s'";
}
foreach my $k ( keys %output ) {
my $file = $k eq "general" ? "base-data.sql" : "base-data-local.sql";
print "Dumping $file\n";
my $ffile = "$ENV{'LJHOME'}/bin/upgrading/$file";
open( F, ">$ffile" ) or die "Can't write to $ffile\n";
print F header_text();
foreach ( sort { $a->[0] cmp $b->[0] } @{ $output{$k} } ) {
print F $_->[1];
}
close F;
}
# dump proplists, etc
print "Dumping proplists.dat\n";
open( my $plg, ">$ENV{LJHOME}/bin/upgrading/proplists.dat" ) or die;
@ -181,7 +62,7 @@ foreach my $table ( 'userproplist', 'talkproplist', 'logproplist', 'usermsgpropl
# and dump mood info
print "Dumping moods.dat\n";
open( F, ">$ENV{'LJHOME'}/bin/upgrading/moods.dat" ) or die;
$sth = $dbh->prepare("SELECT moodid, mood, parentmood FROM moods ORDER BY moodid");
my $sth = $dbh->prepare("SELECT moodid, mood, parentmood FROM moods ORDER BY moodid");
$sth->execute;
while ( @_ = $sth->fetchrow_array ) {
print F "MOOD @_\n";

View File

@ -1,5 +1,4 @@
# This file is automatically generated from MySQL by $LJHOME/bin/dumpsql.pl
# Don't submit a diff against a hand-modified file - dump and diff instead.
# This file is no longer automatically generated by $LJHOME/bin/dumpsql.pl
REPLACE INTO codes (code, item, sortorder, type) VALUES ('#000000', 'Black', '210', 'color');
REPLACE INTO codes (code, item, sortorder, type) VALUES ('#0000FF', 'Blue, Bright', '150', 'color');
@ -260,6 +259,7 @@ REPLACE INTO codes (code, item, sortorder, type) VALUES ('th', 'Thueringen', '0'
REPLACE INTO codes (code, item, sortorder, type) VALUES ('vi', 'Victoria', '0', 'stateau');
REPLACE INTO codes (code, item, sortorder, type) VALUES ('wa', 'Western Australia', '0', 'stateau');
REPLACE INTO codes (code, item, sortorder, type) VALUES ('yt', 'Yukon Territory', '0', 'stateca');
INSERT IGNORE INTO priv_list (des, is_public, privcode, privname, scope) VALUES ('Allows a user to grant or revoke privileges to/from other users. arg=Unique privcode that the user has access to grant/deny for, or \"*\" for all privileges.', '0', 'admin', 'Administer privileges', 'general');
UPDATE priv_list SET des='Allows a user to grant or revoke privileges to/from other users. arg=Unique privcode that the user has access to grant/deny for, or \"*\" for all privileges.',is_public='0',privname='Administer privileges',scope='general' WHERE privcode='admin';
INSERT IGNORE INTO priv_list (des, is_public, privcode, privname, scope) VALUES ('Allows a user to view information that isn\'t otherwise available. All use is logged. arg=Arg=\"*\": can view everything, Arg=\"suspended\": can view public posts in a suspended journal, Arg=\"userlog\": can see userlog data.', '0', 'canview', 'View All Entries', 'general');
@ -320,6 +320,7 @@ INSERT IGNORE INTO priv_list (des, is_public, privcode, privname, scope) VALUES
UPDATE priv_list SET des='Allows a user to edit site text in a given language. arg=Unique language code, optionally appended by |domainid.domaincode',is_public='1',privname='Translate/Update Text',scope='general' WHERE privcode='translate';
INSERT IGNORE INTO priv_list (des, is_public, privcode, privname, scope) VALUES ('Allows a user to add/edit vgifts. arg=Tag in case restricting priv to a particular category is needed, or "*" for all tags.', '1', 'vgifts', 'Virtual Gifts', 'general');
UPDATE priv_list SET des='Allows a user to add/edit vgifts. arg=Tag in case restricting priv to a particular category is needed, or "*" for all tags.',is_public='1',privname='Virtual Gifts',scope='general' WHERE privcode='vgifts';
INSERT IGNORE INTO ratelist (des, name) VALUES ('Logged when a user adds someone to their Friends list', 'addfriend');
UPDATE ratelist SET des='Logged when a user adds someone to their Friends list' WHERE name='addfriend';
INSERT IGNORE INTO ratelist (des, name) VALUES ('Logged when a user creates a community.', 'commcreate');
@ -336,5 +337,59 @@ INSERT IGNORE INTO ratelist (des, name) VALUES ('Logged when a users sends a mes
UPDATE ratelist SET des='Logged when a users sends a message via Tell A Friend' WHERE name='tellafriend';
INSERT IGNORE INTO ratelist (des, name) VALUES ('Logged when a users sends a message to another user', 'usermessage');
UPDATE ratelist SET des='Logged when a users sends a message to another user' WHERE name='usermessage';
INSERT IGNORE INTO supportcat (allow_screened, basepoints, catkey, catname, hide_helpers, is_selectable, no_autoreply, public_help, public_read, replyaddress, scope, sortorder, user_closeable) VALUES ('1', '1', 'general', 'General/Unknown', '0', '1', '0', '0', '1', NULL, 'general', '2', '1');
UPDATE supportcat SET allow_screened='1',basepoints='1',catname='General/Unknown',hide_helpers='0',is_selectable='1',no_autoreply='0',public_help='0',public_read='1',replyaddress=NULL,scope='general',sortorder='2',user_closeable='1' WHERE catkey='general';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('ao3', 'ao3', 'ao3.png', 'profile.service.ao3', '//archiveofourown.org/users/%s', 40);
UPDATE profile_services SET userprop='ao3', imgfile='ao3.png', title_ml='profile.service.ao3', url_format='//archiveofourown.org/users/%s', maxlen=40 WHERE name='ao3';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('deviantart', 'deviantart', 'deviantart.png', 'profile.service.deviantart', '//%s.deviantart.com', 20);
UPDATE profile_services SET userprop='deviantart', imgfile='deviantart.png', title_ml='profile.service.deviantart', url_format='//%s.deviantart.com', maxlen=20 WHERE name='deviantart';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('diigo', 'diigo', 'diigo.png', 'profile.service.diigo', '//www.diigo.com/user/%s', 16);
UPDATE profile_services SET userprop='diigo', imgfile='diigo.png', title_ml='profile.service.diigo', url_format='//www.diigo.com/user/%s', maxlen=16 WHERE name='diigo';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('discord', 'discord', 'discord.png', 'profile.service.discord', NULL, 40);
UPDATE profile_services SET userprop='discord', imgfile='discord.png', title_ml='profile.service.discord', url_format=NULL, maxlen=40 WHERE name='discord';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('etsy', 'etsy', 'etsy.png', 'profile.service.etsy', '//www.etsy.com/people/%s', 20);
UPDATE profile_services SET userprop='etsy', imgfile='etsy.png', title_ml='profile.service.etsy', url_format='//www.etsy.com/people/%s', maxlen=20 WHERE name='etsy';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('ffnet', 'ffnet', 'ffnet.png', 'profile.service.ffnet', '//www.fanfiction.net/~%s', 30);
UPDATE profile_services SET userprop='ffnet', imgfile='ffnet.png', title_ml='profile.service.ffnet', url_format='//www.fanfiction.net/~%s', maxlen=30 WHERE name='ffnet';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('github', 'github', 'github.png', 'profile.service.github', '//github.com/%s', 39);
UPDATE profile_services SET userprop='github', imgfile='github.png', title_ml='profile.service.github', url_format='//github.com/%s', maxlen=39 WHERE name='github';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('google_chat', 'google_talk', 'google_hangouts.png', 'profile.service.hangouts', NULL, 60);
UPDATE profile_services SET userprop='google_talk', imgfile='google_hangouts.png', title_ml='profile.service.hangouts', url_format=NULL, maxlen=60 WHERE name='google_chat';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('icq', 'icq', 'icq.gif', 'profile.service.icq', '//wwp.icq.com/%s', 12);
UPDATE profile_services SET userprop='icq', imgfile='icq.gif', title_ml='profile.service.icq', url_format='//wwp.icq.com/%s', maxlen=12 WHERE name='icq';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('insanejournal', 'insanejournal', 'insanejournal.png', 'profile.service.insanejournal', '//%s.insanejournal.com', 15);
UPDATE profile_services SET userprop='insanejournal', imgfile='insanejournal.png', title_ml='profile.service.insanejournal', url_format='//%s.insanejournal.com', maxlen=15 WHERE name='insanejournal';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('instagram', 'instagram', 'instagram.png', 'profile.service.instagram', '//www.instagram.com/%s', 30);
UPDATE profile_services SET userprop='instagram', imgfile='instagram.png', title_ml='profile.service.instagram', url_format='//www.instagram.com/%s', maxlen=30 WHERE name='instagram';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('jabber', 'jabber', 'jabber.gif', 'profile.service.jabber', NULL, 60);
UPDATE profile_services SET userprop='jabber', imgfile='jabber.gif', title_ml='profile.service.jabber', url_format=NULL, maxlen=60 WHERE name='jabber';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('lastfm', 'last_fm_user', 'lastfm.gif', 'profile.service.lastfm', '//www.last.fm/user/%s', 255);
UPDATE profile_services SET userprop='last_fm_user', imgfile='lastfm.gif', title_ml='profile.service.lastfm', url_format='//www.last.fm/user/%s', maxlen=255 WHERE name='lastfm';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('livejournal', 'livejournal', 'livejournal.gif', 'profile.service.livejournal', '//%s.livejournal.com', 30);
UPDATE profile_services SET userprop='livejournal', imgfile='livejournal.gif', title_ml='profile.service.livejournal', url_format='//%s.livejournal.com', maxlen=30 WHERE name='livejournal';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('medium', 'medium', 'medium.png', 'profile.service.medium', '//medium.com/@%s/latest', 25);
UPDATE profile_services SET userprop='medium', imgfile='medium.png', title_ml='profile.service.medium', url_format='//medium.com/@%s/latest', maxlen=25 WHERE name='medium';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('patreon', 'patreon', 'patreon.png', 'profile.service.patreon', '//www.patreon.com/%s', 255);
UPDATE profile_services SET userprop='patreon', imgfile='patreon.png', title_ml='profile.service.patreon', url_format='//www.patreon.com/%s', maxlen=255 WHERE name='patreon';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('pillowfort', 'pillowfort', 'pillowfort.png', 'profile.service.pillowfort', '//www.pillowfort.social/%s', 255);
UPDATE profile_services SET userprop='pillowfort', imgfile='pillowfort.png', title_ml='profile.service.pillowfort', url_format='//www.pillowfort.social/%s', maxlen=255 WHERE name='pillowfort';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('pinboard', 'pinboard', 'pinboard.png', 'profile.service.pinboard', '//pinboard.in/u:%s', 30);
UPDATE profile_services SET userprop='pinboard', imgfile='pinboard.png', title_ml='profile.service.pinboard', url_format='//pinboard.in/u:%s', maxlen=30 WHERE name='pinboard';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('pinterest', 'pinterest', 'pinterest.png', 'profile.service.pinterest', '//www.pinterest.com/%s', 30);
UPDATE profile_services SET userprop='pinterest', imgfile='pinterest.png', title_ml='profile.service.pinterest', url_format='//www.pinterest.com/%s', maxlen=30 WHERE name='pinterest';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('plurk', 'plurk', 'plurk.png', 'profile.service.plurk', '//www.plurk.com/%s', 255);
UPDATE profile_services SET userprop='plurk', imgfile='plurk.png', title_ml='profile.service.plurk', url_format='//www.plurk.com/%s', maxlen=255 WHERE name='plurk';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('ravelry', 'ravelry', 'ravelry.png', 'profile.service.ravelry', '//www.ravelry.com/people/%s', 40);
UPDATE profile_services SET userprop='ravelry', imgfile='ravelry.png', title_ml='profile.service.ravelry', url_format='//www.ravelry.com/people/%s', maxlen=40 WHERE name='ravelry';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('reddit', 'reddit', 'reddit.png', 'profile.service.reddit', '//www.reddit.com/user/%s', 20);
UPDATE profile_services SET userprop='reddit', imgfile='reddit.png', title_ml='profile.service.reddit', url_format='//www.reddit.com/user/%s', maxlen=20 WHERE name='reddit';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('skype', 'skype', 'skype.gif', 'profile.service.skype', NULL, 40);
UPDATE profile_services SET userprop='skype', imgfile='skype.gif', title_ml='profile.service.skype', url_format=NULL, maxlen=40 WHERE name='skype';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('tumblr', 'tumblr', 'tumblr.png', 'profile.service.tumblr', '//%s.tumblr.com', 255);
UPDATE profile_services SET userprop='tumblr', imgfile='tumblr.png', title_ml='profile.service.tumblr', url_format='//%s.tumblr.com', maxlen=255 WHERE name='tumblr';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('twitter', 'twitter', 'twitter_bird.png', 'profile.service.twitter', '//www.twitter.com/%s', 40);
UPDATE profile_services SET userprop='twitter', imgfile='twitter_bird.png', title_ml='profile.service.twitter', url_format='//www.twitter.com/%s', maxlen=40 WHERE name='twitter';
INSERT IGNORE INTO profile_services (name, userprop, imgfile, title_ml, url_format, maxlen) VALUES ('wattpad', 'wattpad', 'wattpad.png', 'profile.service.wattpad', '//www.wattpad.com/user/%s', 20);
UPDATE profile_services SET userprop='wattpad', imgfile='wattpad.png', title_ml='profile.service.wattpad', url_format='//www.wattpad.com/user/%s', maxlen=20 WHERE name='wattpad';

View File

@ -2297,6 +2297,58 @@ post.security.private.p=The entry is only visible to you (private).
post.security.public=The entry is visible to everyone (public).
profile.service.ao3=Archive of Our Own
profile.service.deviantart=DeviantArt
profile.service.diigo=Diigo
profile.service.discord=Discord
profile.service.etsy=Etsy
profile.service.ffnet=FanFiction.net
profile.service.github=GitHub
profile.service.hangouts=Google Chat
profile.service.icq=ICQ
profile.service.insanejournal=InsaneJournal
profile.service.instagram=Instagram
profile.service.jabber=Jabber
profile.service.lastfm=Last.fm
profile.service.livejournal=LiveJournal
profile.service.medium=Medium
profile.service.patreon=Patreon
profile.service.pillowfort=Pillowfort
profile.service.pinboard=Pinboard
profile.service.pinterest=Pinterest
profile.service.plurk=Plurk
profile.service.ravelry=Ravelry
profile.service.reddit=Reddit
profile.service.skype=Skype
profile.service.tumblr=Tumblr
profile.service.twitter=Twitter
profile.service.wattpad=Wattpad
protocol.bad_password=Your password is too easy to guess. We strongly recommend you change it, otherwise you risk having your journal hijacked. Visit [[siteroot]]/changepassword to change your password.
protocol.mail_bouncing=You are currently using a bad email address. All mail we try to send you is bouncing. We require a valid email address for continued use. Visit the support area for more information.

View File

@ -20,7 +20,7 @@ userproplist.ao3:
des: AO3 user account name
indexed: 0
multihomed: 0
prettyname: Archive of Our Own user account name
prettyname: Archive of Our Own user account name (legacy value)
userproplist.betafeatures_list:
cldversion: 4
@ -196,7 +196,7 @@ userproplist.deviantart:
des: DeviantArt user account name
indexed: 0
multihomed: 0
prettyname: DeviantArt user account name
prettyname: DeviantArt user account name (legacy value)
userproplist.disable_auto_formatting:
cldversion: 4
@ -220,7 +220,7 @@ userproplist.discord:
des: Discord user account name
indexed: 0
multihomed: 0
prettyname: Discord user account name
prettyname: Discord user account name (legacy value)
userproplist.displaydate_check:
cldversion: 4
@ -380,7 +380,7 @@ userproplist.etsy:
des: Etsy user account name
indexed: 0
multihomed: 0
prettyname: Etsy user account name
prettyname: Etsy user account name (legacy value)
userproplist.diigo:
cldversion: 4
@ -388,7 +388,7 @@ userproplist.diigo:
des: Diigo user account name
indexed: 0
multihomed: 0
prettyname: Diigo user account name
prettyname: Diigo user account name (legacy value)
userproplist.exclude_from_own_stats:
cldversion: 4
@ -404,7 +404,7 @@ userproplist.ffnet:
des: FanFiction.net user account name
indexed: 0
multihomed: 0
prettyname: FanFiction.net user account name
prettyname: FanFiction.net user account name (legacy value)
userproplist.friendspagetitle:
cldversion: 4
@ -444,7 +444,7 @@ userproplist.github:
des: GitHub user account name
indexed: 0
multihomed: 0
prettyname: GitHub user account name
prettyname: GitHub user account name (legacy value)
userproplist.google_talk:
cldversion: 0
@ -452,7 +452,7 @@ userproplist.google_talk:
des: Google Chat Service address
indexed: 1
multihomed: 1
prettyname: Google Chat Address
prettyname: Google Chat Address (legacy value)
userproplist.google_analytics:
cldversion: 4
@ -516,7 +516,7 @@ userproplist.icq:
des: ICQ Number
indexed: 1
multihomed: 1
prettyname: ICQ
prettyname: ICQ (legacy value)
userproplist.import_job:
cldversion: 4
@ -556,7 +556,7 @@ userproplist.insanejournal:
des: InsaneJournal account name for user profile
indexed: 0
multihomed: 0
prettyname: InsaneJournal Account
prettyname: InsaneJournal Account (legacy value)
userproplist.instagram:
cldversion: 4
@ -564,7 +564,7 @@ userproplist.instagram:
des: Instagram account name for user profile
indexed: 0
multihomed: 0
prettyname: Instagram Account
prettyname: Instagram Account (legacy value)
userproplist.jabber:
cldversion: 0
@ -572,7 +572,7 @@ userproplist.jabber:
des: Jabber address (username@server)
indexed: 1
multihomed: 1
prettyname: Jabber Address
prettyname: Jabber Address (legacy value)
userproplist.journalsubtitle:
cldversion: 4
@ -620,7 +620,7 @@ userproplist.last_fm_user:
des: LastFM user account name
indexed: 0
multihomed: 0
prettyname: LastFM user account name
prettyname: LastFM user account name (legacy value)
userproplist.livejournal:
cldversion: 4
@ -628,7 +628,7 @@ userproplist.livejournal:
des: LiveJournal user account name
indexed: 0
multihomed: 0
prettyname: LiveJournal user account name
prettyname: LiveJournal user account name (legacy value)
userproplist.latest_optout:
cldversion: 4
@ -644,7 +644,7 @@ userproplist.medium:
des: Medium user account name
indexed: 0
multihomed: 0
prettyname: Medium user account name
prettyname: Medium user account name (legacy value)
userproplist.moderated:
cldversion: 4
@ -1116,7 +1116,7 @@ userproplist.patreon:
des: Patreon user account name
indexed: 0
multihomed: 0
prettyname: Patreon user account name
prettyname: Patreon user account name (legacy value)
userproplist.pillowfort:
cldversion: 4
@ -1124,7 +1124,7 @@ userproplist.pillowfort:
des: Pillowfort user account name
indexed: 0
multihomed: 0
prettyname: Pillowfort user account name
prettyname: Pillowfort user account name (legacy value)
userproplist.pinboard:
cldversion: 4
@ -1132,7 +1132,7 @@ userproplist.pinboard:
des: Pinboard user account name
indexed: 0
multihomed: 0
prettyname: Pinboard user account name
prettyname: Pinboard user account name (legacy value)
userproplist.pinterest:
cldversion: 4
@ -1140,7 +1140,7 @@ userproplist.pinterest:
des: Pinterest user account name
indexed: 0
multihomed: 0
prettyname: Pinterest user account name
prettyname: Pinterest user account name (legacy value)
userproplist.plurk:
cldversion: 4
@ -1148,7 +1148,7 @@ userproplist.plurk:
des: Plurk user account name
indexed: 0
multihomed: 0
prettyname: Plurk user account name
prettyname: Plurk user account name (legacy value)
userproplist.posting_guidelines_entry:
cldversion: 4
@ -1180,7 +1180,7 @@ userproplist.ravelry:
des: Ravelry user account name
indexed: 0
multihomed: 0
prettyname: Ravelry user account name
prettyname: Ravelry user account name (legacy value)
userproplist.reddit:
cldversion: 4
@ -1188,7 +1188,7 @@ userproplist.reddit:
des: Reddit user account name
indexed: 0
multihomed: 0
prettyname: Reddit user account name
prettyname: Reddit user account name (legacy value)
userproplist.renamedto:
cldversion: 4
@ -1292,7 +1292,7 @@ userproplist.skype:
des: Skype Account ID
indexed: 1
multihomed: 1
prettyname: Skype ID
prettyname: Skype ID (legacy value)
userproplist.state:
cldversion: 4
@ -1356,7 +1356,7 @@ userproplist.tumblr:
des: Tumblr user account name
indexed: 0
multihomed: 0
prettyname: Tumblr user account name
prettyname: Tumblr user account name (legacy value)
userproplist.twitter:
cldversion: 4
@ -1364,7 +1364,7 @@ userproplist.twitter:
des: Twitter user account name
indexed: 0
multihomed: 0
prettyname: Twitter user account name
prettyname: Twitter user account name (legacy value)
userproplist.url:
cldversion: 4
@ -1404,7 +1404,7 @@ userproplist.wattpad:
des: Wattpad user account name
indexed: 0
multihomed: 0
prettyname: Wattpad user account name
prettyname: Wattpad user account name (legacy value)
userproplist.who_invited:
cldversion: 4

View File

@ -3120,6 +3120,34 @@ CREATE TABLE `key_prop_list` (
)
EOC
register_tablecreate( "profile_services", <<'EOC');
CREATE TABLE profile_services (
service_id MEDIUMINT(8) UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(40) NOT NULL,
userprop VARCHAR(40),
imgfile VARCHAR(40) NOT NULL,
title_ml VARCHAR(80) NOT NULL,
url_format VARCHAR(255),
maxlen TINYINT(3) UNSIGNED NOT NULL,
PRIMARY KEY (service_id),
UNIQUE KEY (name)
)
EOC
# clustered
register_tablecreate( "user_profile_accts", <<'EOC');
CREATE TABLE user_profile_accts (
userid INT(10) UNSIGNED NOT NULL,
account_id MEDIUMINT(8) UNSIGNED NOT NULL,
service_id MEDIUMINT(8) UNSIGNED NOT NULL,
value VARCHAR(255) NOT NULL,
PRIMARY KEY (userid, account_id),
INDEX (value)
)
EOC
# NOTE: new table declarations go ABOVE here ;)
### changes

152
cgi-bin/DW/External/ProfileServices.pm vendored Normal file
View File

@ -0,0 +1,152 @@
#!/usr/bin/perl
#
# DW::External::ProfileServices
#
# Information on external services referenced on profile pages.
#
# Authors:
# Jen Griffin <kareila@livejournal.com>
#
# Copyright (c) 2009-2023 by Dreamwidth Studios, LLC.
#
# This program is free software; you may redistribute it and/or modify it under
# the same terms as Perl itself. For a copy of the license, please reference
# 'perldoc perlartistic' or 'perldoc perlgpl'.
#
package DW::External::ProfileServices;
use strict;
use warnings;
use Carp qw/ confess /;
sub list {
my ( $class, %opts ) = @_;
# load services from memcache
my $memkey = 'profile_services';
my $services = LJ::MemCache::get($memkey);
return $services if $services;
# load services from database and add to memcache (expiring hourly)
my $dbr = LJ::get_db_reader();
my $data = $dbr->selectall_hashref(
"SELECT service_id, name, userprop, imgfile, title_ml,"
. " url_format, maxlen FROM profile_services",
"name"
);
confess $dbr->errstr if $dbr->err;
$services = [ map { $data->{$_} } sort keys %$data ];
LJ::MemCache::set( $memkey, $services, 3600 );
return $services;
}
sub userprops {
my ( $class, %opts ) = @_;
my $services = $class->list;
my @userprops;
foreach my $site (@$services) {
push @userprops, $site->{userprop} if defined $site->{userprop};
}
return \@userprops;
}
### user methods
sub load_profile_accts {
my ( $u, %args ) = @_;
$u = LJ::want_user($u) or confess 'invalid user object';
my $uid = $u->userid;
# load accounts from memcache
my $memkey = [ $uid, "profile_accts:$uid" ];
my $accounts = LJ::MemCache::get($memkey);
return $accounts if $accounts && !$args{force_db};
$accounts = {};
# load accounts from database and add to memcache (no expiration)
my $dbcr = LJ::get_cluster_reader($u) or die;
my $data = $dbcr->selectall_arrayref(
"SELECT service_id, account_id, value FROM user_profile_accts"
. " WHERE userid=? ORDER BY value",
{ Slice => {} },
$uid
);
confess $dbcr->errstr if $dbcr->err;
foreach my $acct (@$data) {
my $s_id = $acct->{service_id};
$accounts->{$s_id} //= [];
push @{ $accounts->{$s_id} }, [ $acct->{account_id}, $acct->{value} ];
}
LJ::MemCache::set( $memkey, $accounts );
return $accounts;
}
*LJ::User::load_profile_accts = \&load_profile_accts;
*DW::User::load_profile_accts = \&load_profile_accts;
sub save_profile_accts {
my ( $u, $new_accts, %opts ) = @_;
$u = LJ::want_user($u) or confess 'invalid user object';
my $old_accts = $u->load_profile_accts( force_db => 1 );
# expire memcache after updating db
my $uid = $u->userid;
my $memkey = [ $uid, "profile_accts:$uid" ];
return unless $u->writer;
# if %$old_accts is empty, we need to clear out the user's legacy userprops
# to avoid an edge case where if a user clears out all of their accounts
# later, the old userprop values will suddenly reappear on their profile
unless (%$old_accts) {
my $userprops = DW::External::ProfileServices->userprops;
my %prop = map { $_ => '' } @$userprops;
$u->set_prop( \%prop, undef, { skip_db => 1 } );
}
while ( my ( $s_id, $multival ) = each %$new_accts ) {
foreach my $val (@$multival) {
if ( ref $val && $val->[1] ) {
# update the value of the existing row
$u->do(
"UPDATE user_profile_accts SET value = ? WHERE account_id = ? AND userid = ?",
undef, $val->[1], $val->[0], $uid );
}
elsif ( ref $val ) {
# delete the existing row
$u->do( "DELETE FROM user_profile_accts WHERE account_id = ? AND userid = ?",
undef, $val->[0], $uid );
}
else {
# new addition or legacy upgrade
my $a_id = LJ::alloc_user_counter( $u, 'P' );
$u->do(
"INSERT INTO user_profile_accts (userid, account_id, service_id, value)"
. " VALUES (?,?,?,?)",
undef, $uid, $a_id, $s_id, $val
);
}
confess $u->errstr if $u->err;
}
}
LJ::MemCache::delete($memkey);
return 1;
}
*LJ::User::save_profile_accts = \&save_profile_accts;
*DW::User::save_profile_accts = \&save_profile_accts;
1;

View File

@ -21,6 +21,7 @@ package DW::Logic::ProfilePage;
use strict;
use DW::Countries;
use DW::Logic::UserLinkBar;
use DW::External::ProfileServices;
# returns a new profile page object
sub profile_page {
@ -770,318 +771,48 @@ sub external_services {
return [] unless $u->is_personal && ( $u->share_contactinfo($remote) || $self->{viewall} );
# this has gotten big enough that we should improve database efficiency
$u->preload_props(
qw/
ao3 deviantart diigo discord etsy ffnet github google_talk
icq insanejournal instagram jabber last_fm_user livejournal
medium patreon pillowfort pinboard pinterest plurk ravelry
reddit skype tumblr twitter wattpad
/
);
my $info = sub {
my ( $acct, $site ) = @_;
if ( my $ao3 = $u->prop('ao3') ) {
my $url = sprintf( "//archiveofourown.org/users/%s", LJ::eurl($ao3) );
push @ret,
{
type => 'ao3',
text => LJ::ehtml($ao3),
my $url =
defined $site->{url_format}
? sprintf( $site->{url_format}, LJ::eurl($acct) )
: undef;
return {
type => $site->{name},
text => LJ::ehtml($acct),
url => $url,
image => 'ao3.png',
title_ml => '.service.ao3',
};
}
if ( my $deviantart = $u->prop('deviantart') ) {
my $url = sprintf( "//%s.deviantart.com", LJ::eurl($deviantart) );
push @ret,
{
type => 'deviantart',
text => LJ::ehtml($deviantart),
url => $url,
image => 'deviantart.png',
title_ml => '.service.deviantart',
};
}
if ( my $diigo = $u->prop('diigo') ) {
my $url = sprintf( "//www.diigo.com/user/%s", LJ::eurl($diigo) );
push @ret,
{
type => 'diigo',
text => LJ::ehtml($diigo),
url => $url,
image => 'diigo.png',
title_ml => '.service.diigo',
};
}
if ( my $discord = $u->prop('discord') ) {
push @ret,
{
type => 'discord',
text => LJ::ehtml($discord),
image => 'discord.png',
title_ml => '.service.discord',
};
}
if ( my $etsy = $u->prop('etsy') ) {
my $url = sprintf( "//www.etsy.com/people/%s", LJ::eurl($etsy) );
push @ret,
{
type => 'etsy',
text => LJ::ehtml($etsy),
url => $url,
image => 'etsy.png',
title_ml => '.service.etsy',
};
}
if ( my $ffnet = $u->prop('ffnet') ) {
my $url = sprintf( "//www.fanfiction.net/~%s", LJ::eurl($ffnet) );
push @ret,
{
type => 'ffnet',
text => LJ::ehtml($ffnet),
url => $url,
image => 'ffnet.png',
title_ml => '.service.ffnet',
};
}
if ( my $github = $u->prop('github') ) {
my $url = sprintf( "//github.com/%s", LJ::eurl($github) );
push @ret,
{
type => 'github',
text => LJ::ehtml($github),
url => $url,
image => 'github.png',
title_ml => '.service.github',
};
}
if ( my $google = $u->prop('google_talk') ) {
push @ret,
{
type => 'google',
email => LJ::ehtml($google),
image => 'google_hangouts.png',
title_ml => '.service.hangouts',
};
}
if ( my $icq = $u->prop('icq') ) {
my $url = sprintf( "//wwp.icq.com/%s", LJ::eurl($icq) );
push @ret,
{
type => 'icq',
text => LJ::ehtml($icq),
url => $url,
image => 'icq.gif',
title_ml => '.service.icq',
};
}
if ( my $insanejournal = $u->prop('insanejournal') ) {
my $url = sprintf( "//%s.insanejournal.com", LJ::eurl($insanejournal) );
push @ret,
{
type => 'insanejournal',
text => LJ::ehtml($insanejournal),
url => $url,
image => 'insanejournal.png',
title_ml => '.service.insanejournal',
};
}
if ( my $instagram = $u->prop('instagram') ) {
my $url = sprintf( "//www.instagram.com/%s", LJ::eurl($instagram) );
push @ret,
{
type => 'instagram',
text => LJ::ehtml($instagram),
url => $url,
image => 'instagram.png',
title_ml => '.service.instagram',
};
}
if ( my $jabber = $u->prop('jabber') ) {
push @ret,
{
type => 'jabber',
email => LJ::ehtml($jabber),
image => 'jabber.gif',
title_ml => '.service.jabber',
};
}
if ( my $lastfm = $u->prop('last_fm_user') ) {
my $url = sprintf( "//www.last.fm/user/%s", LJ::eurl($lastfm) );
push @ret,
{
type => 'lastfm',
text => LJ::ehtml($lastfm),
url => $url,
image => 'lastfm.gif',
title_ml => '.service.lastfm',
};
}
if ( my $livejournal = $u->prop('livejournal') ) {
my $url = sprintf( "//%s.livejournal.com", LJ::eurl($livejournal) );
push @ret,
{
type => 'livejournal',
text => LJ::ehtml($livejournal),
url => $url,
image => 'livejournal.gif',
title_ml => '.service.livejournal',
};
}
if ( my $medium = $u->prop('medium') ) {
my $url = sprintf( "//medium.com/@%s/latest", LJ::eurl($medium) );
push @ret,
{
type => 'medium',
text => LJ::ehtml($medium),
url => $url,
image => 'medium.png',
title_ml => '.service.medium',
};
}
if ( my $patreon = $u->prop('patreon') ) {
my $url = sprintf( "//www.patreon.com/%s", LJ::eurl($patreon) );
push @ret,
{
type => 'patreon',
text => LJ::ehtml($patreon),
url => $url,
image => 'patreon.png',
title_ml => '.service.patreon',
};
}
if ( my $pillowfort = $u->prop('pillowfort') ) {
my $url = sprintf( "//www.pillowfort.social/%s", LJ::eurl($pillowfort) );
push @ret,
{
type => 'pillowfort',
text => LJ::ehtml($pillowfort),
url => $url,
image => 'pillowfort.png',
title_ml => '.service.pillowfort',
};
}
if ( my $pinboard = $u->prop('pinboard') ) {
my $url = sprintf( "//pinboard.in/u:%s", LJ::eurl($pinboard) );
push @ret,
{
type => 'pinboard',
text => LJ::ehtml($pinboard),
url => $url,
image => 'pinboard.png',
title_ml => '.service.pinboard',
};
}
if ( my $pinterest = $u->prop('pinterest') ) {
my $url = sprintf( "//www.pinterest.com/%s", LJ::eurl($pinterest) );
push @ret,
{
type => 'pinterest',
text => LJ::ehtml($pinterest),
url => $url,
image => 'pinterest.png',
title_ml => '.service.pinterest',
};
}
if ( my $plurk = $u->prop('plurk') ) {
my $url = sprintf( "//www.plurk.com/%s", LJ::eurl($plurk) );
push @ret,
{
type => 'plurk',
text => LJ::ehtml($plurk),
url => $url,
image => 'plurk.png',
title_ml => '.service.plurk',
};
}
if ( my $ravelry = $u->prop('ravelry') ) {
my $url = sprintf( "//www.ravelry.com/people/%s", LJ::eurl($ravelry) );
push @ret,
{
type => 'ravelry',
text => LJ::ehtml($ravelry),
url => $url,
image => 'ravelry.png',
title_ml => '.service.ravelry',
};
}
if ( my $reddit = $u->prop('reddit') ) {
my $url = sprintf( "//www.reddit.com/user/%s", LJ::eurl($reddit) );
push @ret,
{
type => 'reddit',
text => LJ::ehtml($reddit),
url => $url,
image => 'reddit.png',
title_ml => '.service.reddit',
};
}
if ( my $skype = $u->prop('skype') ) {
my $service = {
type => 'skype',
email => LJ::ehtml($skype),
image => 'skype.gif',
title_ml => '.service.skype',
image => $site->{imgfile},
title_ml => $site->{title_ml},
};
push @ret, $service;
};
my $services = DW::External::ProfileServices->list;
my $accounts = $u->load_profile_accts;
if (%$accounts) {
foreach my $site (@$services) {
my $s_id = $site->{service_id};
my $vals = $accounts->{$s_id} // [];
foreach my $acct (@$vals) {
push @ret, $info->( $acct->[1], $site );
}
}
return \@ret;
}
if ( my $tumblr = $u->prop('tumblr') ) {
my $url = sprintf( "//%s.tumblr.com", LJ::eurl($tumblr) );
push @ret,
{
type => 'tumblr',
text => LJ::ehtml($tumblr),
url => $url,
image => 'tumblr.png',
title_ml => '.service.tumblr',
};
}
# legacy path for users who still have data in userprops
my $userprops = DW::External::ProfileServices->userprops;
if ( my $twitter = $u->prop('twitter') ) {
my $url = sprintf( "//www.twitter.com/%s", LJ::eurl($twitter) );
push @ret,
{
type => 'twitter',
text => LJ::ehtml($twitter),
url => $url,
image => 'twitter_bird.png',
title_ml => '.service.twitter',
};
}
$u->preload_props(@$userprops);
if ( my $wattpad = $u->prop('wattpad') ) {
my $url = sprintf( "//www.wattpad.com/user/%s", LJ::eurl($wattpad) );
push @ret,
{
type => 'wattpad',
text => LJ::ehtml($wattpad),
url => $url,
image => 'wattpad.png',
title_ml => '.service.wattpad',
};
foreach my $site (@$services) {
if ( my $acct = $u->prop( $site->{userprop} ) ) {
push @ret, $info->( $acct, $site );
}
}
return \@ret;

View File

@ -56,7 +56,7 @@ $LJ::DBIRole = new DBI::Role {
"notifybookmarks", "embedcontent_preview", "logprop_history", "import_status",
"externalaccount", "content_filters", "content_filter_data", "userpicmap3",
"media", "collections", "collection_items", "logslugs",
"media_versions", "media_props",
"media_versions", "media_props", "user_profile_accts",
);
# keep track of what db locks we have out
@ -535,8 +535,10 @@ sub alloc_global_counter {
# 'Z' == import status item, 'X' == eXternal account
# 'F' == filter id, 'Y' = pic/keYword mapping id
# 'A' == mediA item id, 'O' == cOllection id,
# 'N' == collectioN item id
# 'B' == api key id
# 'N' == collectioN item id, 'B' == api key id,
# 'P' == Profile account id
#
# remaining unused letters: G H J U W
#
sub alloc_user_counter {
my ( $u, $dom, $opts ) = @_;
@ -544,7 +546,7 @@ sub alloc_user_counter {
##################################################################
# IF YOU UPDATE THIS MAKE SURE YOU ADD INITIALIZATION CODE BELOW #
return undef unless $dom =~ /^[LTMPSRKCOVEQGDIZXFYAB]$/; #
return undef unless $dom =~ /^[LTMPSRKCOVEQDIZXFYABN]$/;
##################################################################
my $dbh = LJ::get_db_writer();
@ -704,6 +706,11 @@ sub alloc_user_counter {
$u->selectrow_array( "SELECT MAX(colitemid) FROM collection_items WHERE userid = ?",
undef, $uid );
}
elsif ( $dom eq "P" ) {
$newmax =
$u->selectrow_array( "SELECT MAX(account_id) FROM user_profile_accts WHERE userid = ?",
undef, $uid );
}
else {
die "No user counter initializer defined for area '$dom'.\n";
}

View File

@ -132,3 +132,5 @@ invite_code_try_ip:<ip> = stores a value of 1 for the IP address of the person w
<uid> api_keys_list:<uid> = arrayref of all keyhashes owned by a given userid
profile_editors = arrayref of uids used by profile_save hook
profile_services = arrayref of sorted hashrefs returned from profile_services table
<uid> profile_accts:<uid> = hashref of user data from user_profile_accts table

View File

@ -20,6 +20,7 @@ body<=
use strict;
use vars qw(%POST %GET $headextra @errors);
use LJ::Setting;
use DW::External::ProfileServices;
LJ::need_res( { priority => $LJ::OLD_RES_PRIORITY }, 'stc/lj_settings.css' );
@ -47,17 +48,12 @@ body<=
push @settings, "LJ::Setting::FindByEmail" if LJ::is_enabled('opt_findbyemail');
push @settings, "DW::Setting::ProfileEmail";
my $dbr = LJ::get_db_reader();
my $sth;
my $profile_accts = $u->load_profile_accts( force_db => 1 );
# list the userprops that are handled explicitly by code on this page
# props in this list will be preloaded on page load and saved on post
my @uprops = qw/
opt_whatemailshow comm_theme
ao3 deviantart diigo discord etsy ffnet github google_talk
icq insanejournal instagram jabber last_fm_user livejournal
medium patreon pillowfort pinboard pinterest plurk ravelry
reddit skype tumblr twitter wattpad
url urlname gender
opt_hidefriendofs opt_hidememberofs
sidx_bdate sidx_bday
@ -66,6 +62,12 @@ body<=
opt_sharebday
/;
my %legacy_service_props = map { $_ => 1 } @{ DW::External::ProfileServices->userprops };
unless (%$profile_accts) {
push @uprops, $_ foreach keys %legacy_service_props;
}
# load user props
$u->preload_props( { use_master => 1 }, @uprops );
@ -403,45 +405,65 @@ body<=
$ret .= "<tr><td colspan=3><table summary=''>";
my $oddeven = 0;
my $ct = 1;
foreach my $p (
["ao3", $ML{ '.services.ao3' }, 40],
["deviantart", $ML{'.services.deviantart'}, 20],
["diigo", $ML{ '.services.diigo' }, 16],
["discord", $ML{ '.services.discord' }, 40],
["etsy", $ML{ '.services.etsy' }, 20],
["ffnet", $ML{ '.services.ffnet' }, 30],
["github", $ML{'.services.github'}, 39],
["google_talk", $ML{'.chat.googlehangouts'}, 60],
["icq", $ML{'.chat.icquin'}, 12],
["insanejournal", $ML{'.services.insanejournal'}, 15],
["instagram", $ML{'.services.instagram'}, 30],
["jabber", $ML{'.chat.jabber'}, 60],
["last_fm_user", $ML{'.services.last_fm'}, 255],
["livejournal", $ML{'.services.livejournal'}, 30],
["medium", $ML{'.services.medium'}, 25],
["patreon", $ML{'.services.patreon'}, 255],
["pillowfort", $ML{'.services.pillowfort'}, 255],
["pinboard", $ML{'.services.pinboard'}, 30],
["pinterest", $ML{'.services.pinterest'}, 30],
["plurk", $ML{ '.services.plurk' }, 255],
["ravelry", $ML{ '.services.ravelry' }, 40],
["reddit", $ML{ '.services.reddit' }, 20],
["skype", $ML{'.chat.skype'}, 40],
["tumblr", $ML{ '.services.tumblr' }, 255],
["twitter", $ML{ '.services.twitter' }, 40],
["wattpad", $ML{ '.services.wattpad' }, 20],
)
my $service_info = sub {
my ($site) = @_;
$site->{title} = LJ::Lang::ml( $site->{title_ml} );
return $site;
};
my @services = map $service_info->($_), @{ DW::External::ProfileServices->list };
my @dropdown = ( '' => '' );
push @dropdown, ( $_->{service_id} => $_->{title} ) foreach @services;
my $service_td = sub {
my ( $p, $val ) = @_;
$oddeven = !$oddeven;
$ret .= "<tr class='field_block'>" if $oddeven;
$ret .= "<td class='field_name' width='20%'>$p->{title}</td><td width='30%'>";
$ret .= LJ::html_hidden( "extservice_site_$ct", $p->{service_id} );
$ret .= LJ::html_hidden( "extservice_dbid_$ct", $val->[0] ) if $val->[0];
$ret .= LJ::html_text( { name => "extservice_val_" . $ct++,
value => $val->[1],
title => $p->{title} . " Username",
size => '20',
maxlength => $p->{maxlen} } );
$ret .= "</td>\n";
$ret .= "</tr>" unless $oddeven;
};
# ignore legacy userprops if %$profile_accts has been populated
if (%$profile_accts) {
foreach my $p ( @services ) {
my $s_id = $p->{service_id};
next unless $profile_accts->{$s_id};
foreach my $val ( @{ $profile_accts->{$s_id} } ) {
$service_td->( $p, $val ) ;
}
}
} else {
foreach my $p ( @services )
{
next unless $p->{userprop};
my $val = $u->{$p->{userprop}};
next unless $val;
$service_td->( $p, [0, $val] ) ;
}
}
foreach my $n ( $ct .. 26 ) # same number of fields as static form
{
$oddeven = !$oddeven;
$ret .= "<tr class='field_block'>" if $oddeven;
$ret .= "<td class='field_name' width='20%'>$p->[1]</td><td width='30%'>";
$ret .= LJ::html_text( { name => $p->[0],
value => $u->{$p->[0]},
title => $p->[1],
$ret .= "<td class='field_name' width='20%'>";
$ret .= LJ::html_select( { name => "extservice_site_$n" }, @dropdown );
$ret .= "</td><td width='30%'>";
$ret .= LJ::html_text( { name => "extservice_val_$n",
value => '',
title => "Username \#$n",
size => '20',
maxlength => $p->[2] } );
maxlength => 255 } );
$ret .= "</td>\n";
$ret .= "</tr>" unless $oddeven;
}
@ -693,12 +715,32 @@ body<=
# set userprops
my %prop;
foreach my $uprop (@uprops) {
next if $legacy_service_props{$uprop};
my $eff_val = $POST{$uprop}; # effective value, since 0 isn't stored
$eff_val = "" unless $eff_val;
$prop{$uprop} = $eff_val;
}
$u->set_prop( \%prop, undef, { skip_db => 1 } );
# update external services
my %services = map { $_->{service_id} => $_ } @{ DW::External::ProfileServices->list };
my %new_accts;
foreach my $ct (1..26) {
my $s_id = $POST{"extservice_site_$ct"};
next unless $s_id;
my $val = $POST{"extservice_val_$ct"} // '';
$val = LJ::text_trim( $val, 255, $services{$s_id}->{maxlen} );
$new_accts{$s_id} //= [];
if ( my $a_id = $POST{"extservice_dbid_$ct"} ) {
push @{ $new_accts{$s_id} }, [ $a_id, $val ];
} else {
push @{ $new_accts{$s_id} }, $val;
}
}
$u->save_profile_accts( \%new_accts );
# location or bday could've changed... (who cares about checking exactly)
$u->invalidate_directory_record;

View File

@ -1,12 +1,4 @@
;; -*- coding: utf-8 -*-
.chat.googlehangouts=Google Chat
.chat.icquin=ICQ UIN
.chat.jabber=Jabber
.chat.skype=Skype
.comms2=Display communities of which you are a member or administrator.
.display.title=Display Settings for [[name]]
@ -179,50 +171,6 @@
.select.provider=-- Select Carrier --
.services.ao3=AO3 Username
.services.deviantart=DeviantArt Username
.services.diigo=Diigo Username
.services.discord=Discord Username
.services.etsy=Etsy Username
.services.ffnet=FanFiction.net Username
.services.github=GitHub Username
.services.insanejournal=InsaneJournal Username
.services.instagram=Instagram Username
.services.last_fm=Last.fm Username
.services.livejournal=LiveJournal Username
.services.medium=Medium Username
.services.patreon=Patreon Username
.services.pillowfort=Pillowfort Username
.services.pinboard=Pinboard Username
.services.pinterest=Pinterest Username
.services.plurk=Plurk Username
.services.ravelry=Ravelry Username
.services.reddit=Reddit Username
.services.tumblr=Tumblr Username
.services.twitter=Twitter Username
.services.wattpad=Wattpad Username
.show.birthday.day2=Show only month and day
.show.birthday.full2=Show month, day, and year

View File

@ -101,58 +101,6 @@
.section.edit=Edit
.service.ao3=Archive of Our Own
.service.deviantart=DeviantArt
.service.diigo=Diigo
.service.discord=Discord
.service.etsy=Etsy
.service.ffnet=FanFiction.net
.service.github=GitHub
.service.hangouts=Google Chat
.service.icq=ICQ
.service.insanejournal=InsaneJournal
.service.instagram=Instagram
.service.jabber=Jabber
.service.lastfm=Last.fm
.service.livejournal=LiveJournal
.service.medium=Medium
.service.patreon=Patreon
.service.pillowfort=Pillowfort
.service.pinboard=Pinboard
.service.pinterest=Pinterest
.service.plurk=Plurk
.service.ravelry=Ravelry
.service.reddit=Reddit
.service.skype=Skype
.service.tumblr=Tumblr
.service.twitter=Twitter
.service.wattpad=Wattpad
.title=Profile
.title.communityprofile=Community Profile