--- /dev/null
+/*
+
+Jappix - An open social platform
+These are the vCard JS scripts for Jappix
+
+-------------------------------------------------
+
+License: AGPL
+Author: Vanaryon
+Last revision: 16/01/12
+
+*/
+
+// Opens the vCard popup
+function openVCard() {
+ // Popup HTML content
+ var html =
+ '<div class="top">' + _e("Your profile") + '</div>' +
+
+ '<div class="tab">' +
+ '<a href="#" class="tab-active" data-key="1">' + _e("Identity") + '</a>' +
+ '<a href="#" data-key="2">' + _e("Profile image") + '</a>' +
+ '<a href="#" data-key="3">' + _e("Others") + '</a>' +
+ '</div>' +
+
+ '<div class="content">' +
+ '<div id="lap1" class="lap-active one-lap forms">' +
+ '<fieldset>' +
+ '<legend>' + _e("Personal") + '</legend>' +
+
+ '<label for="USER-FN">' + _e("Complete name") + '</label>' +
+ '<input type="text" id="USER-FN" class="vcard-item" />' +
+
+ '<label for="USER-NICKNAME">' + _e("Nickname") + '</label>' +
+ '<input type="text" id="USER-NICKNAME" class="vcard-item" />' +
+
+ '<label for="USER-N-GIVEN">' + _e("First name") + '</label>' +
+ '<input type="text" id="USER-N-GIVEN" class="vcard-item" />' +
+
+ '<label for="USER-N-FAMILY">' + _e("Last name") + '</label>' +
+ '<input type="text" id="USER-N-FAMILY" class="vcard-item" />' +
+
+ '<label for="USER-BDAY">' + _e("Date of birth") + '</label>' +
+ '<input type="text" id="USER-BDAY" class="vcard-item" />' +
+ '</fieldset>' +
+
+ '<fieldset>' +
+ '<legend>' + _e("Contact") + '</legend>' +
+
+ '<label for="USER-EMAIL-USERID">' + _e("E-mail") + '</label>' +
+ '<input type="text" id="USER-EMAIL-USERID" class="vcard-item" />' +
+
+ '<label for="USER-TEL-NUMBER">' + _e("Phone") + '</label>' +
+ '<input type="text" id="USER-TEL-NUMBER" class="vcard-item" />' +
+
+ '<label for="USER-URL">' + _e("Website") + '</label>' +
+ '<input type="text" id="USER-URL" class="vcard-item" />' +
+ '</fieldset>' +
+ '</div>' +
+
+ '<div id="lap2" class="one-lap forms">' +
+ '<fieldset>' +
+ '<legend>' + _e("New") + '</legend>' +
+
+ '<input type="hidden" id="USER-PHOTO-TYPE" class="vcard-item" />' +
+ '<input type="hidden" id="USER-PHOTO-BINVAL" class="vcard-item" />' +
+
+ '<form id="vcard-avatar" action="./php/avatar-upload.php" method="post" enctype="multipart/form-data">' +
+ generateFileShare() +
+ '</form>' +
+ '</fieldset>' +
+
+ '<fieldset>' +
+ '<legend>' + _e("Current") + '</legend>' +
+
+ '<div class="avatar-container"></div>' +
+
+ '<a href="#" class="one-button avatar-delete talk-images">' + _e("Delete") + '</a>' +
+ '<div class="no-avatar">' + _e("What a pity! You have no profile image defined in your identity card!") + '</div>' +
+ '</fieldset>' +
+
+ '<div class="avatar-wait avatar-info">' + _e("Please wait while your avatar is uploaded...") + '</div>' +
+ '<div class="avatar-ok avatar-info">' + _e("Here it is! A new beautiful profile image!") + '</div>' +
+ '<div class="avatar-error avatar-info">' + _e("The image file is not supported or has a bad size.") + '</div>' +
+ '</div>' +
+
+ '<div id="lap3" class="one-lap forms">' +
+ '<fieldset>' +
+ '<legend>' + _e("Address") + '</legend>' +
+
+ '<label for="USER-ADR-STREET">' + _e("Street") + '</label>' +
+ '<input type="text" id="USER-ADR-STREET" class="vcard-item" />' +
+
+ '<label for="USER-ADR-LOCALITY">' + _e("City") + '</label>' +
+ '<input type="text" id="USER-ADR-LOCALITY" class="vcard-item" />' +
+
+ '<label for="USER-ADR-PCODE">' + _e("Postal code") + '</label>' +
+ '<input type="text" id="USER-ADR-PCODE" class="vcard-item" />' +
+
+ '<label for="USER-ADR-CTRY">' + _e("Country") + '</label>' +
+ '<input type="text" id="USER-ADR-CTRY" class="vcard-item" />' +
+ '</fieldset>' +
+
+ '<fieldset>' +
+ '<legend>' + _e("Biography") + '</legend>' +
+
+ '<textarea id="USER-DESC" rows="8" cols="60" class="vcard-item"></textarea>' +
+ '</fieldset>' +
+ '</div>' +
+
+ '<div class="infos">' +
+ '<p class="infos-title">' + _e("Important notice") + '</p>' +
+
+ '<p>' + _e("Be careful of the information you write into your profile, because it could be accessed by everyone (even someone you don't want to).") + '</p>' +
+ '<p>' + _e("Not everything is private on XMPP; this is one of those things, your public profile (vCard).") + '</p>' +
+ '<p>' + printf(_e("It is strongly recommended to upload a profile image (%s maximum), like a picture of yourself, because that makes you easily recognizable by your friends."), JAPPIX_MAX_UPLOAD) + '</p>' +
+ '<p><a href="https://me.jappix.com/new" target="_blank">' + _e("Enable my public profile") + ' ยป</a></p>' +
+ '</div>' +
+ '</div>' +
+
+ '<div class="bottom">' +
+ '<div class="wait wait-medium"></div>' +
+
+ '<a href="#" class="finish save disabled">' + _e("Save") + '</a>' +
+ '<a href="#" class="finish cancel">' + _e("Cancel") + '</a>' +
+ '</div>';
+
+ // Create the popup
+ createPopup('vcard', html);
+
+ // Associate the events
+ launchVCard();
+
+ // We get the VCard informations
+ getVCard(getXID(), 'user');
+
+ return false;
+}
+
+// Closes the vCard popup
+function closeVCard() {
+ // Destroy the popup
+ destroyPopup('vcard');
+
+ // Create the welcome end popup?
+ if(END_WELCOME)
+ openMe();
+
+ return false;
+}
+
+// Switches the vCard popup tabs
+function switchVCard(id) {
+ $('#vcard .one-lap').removeClass('lap-active');
+ $('#vcard #lap' + id).addClass('lap-active');
+ $('#vcard .tab a').removeClass('tab-active');
+ $('#vcard .tab a[data-key=' + id + ']').addClass('tab-active');
+
+ return false;
+}
+
+// Waits for the avatar upload reply
+function waitAvatarUpload() {
+ // Reset the avatar info
+ $('#vcard .avatar-info').hide().stopTime();
+
+ // Show the wait info
+ $('#vcard .avatar-wait').show();
+}
+
+// Handles the avatar upload reply
+function handleAvatarUpload(responseXML) {
+ // Data selector
+ var dData = $(responseXML).find('jappix');
+
+ // Not current upload session?
+ if(parseInt(dData.attr('id')) != parseInt($('#vcard-avatar input[name=id]').val()))
+ return;
+
+ // Reset the avatar info
+ $('#vcard .avatar-info').hide().stopTime();
+
+ // Process the returned data
+ if(!dData.find('error').size()) {
+ // Read the values
+ var aType = dData.find('type').text();
+ var aBinval = dData.find('binval').text();
+
+ // We remove everything that isn't useful right here
+ $('#vcard .no-avatar').hide();
+ $('#vcard .avatar').remove();
+
+ // We display the delete button
+ $('#vcard .avatar-delete').show();
+
+ // We tell the user it's okay
+ $('#vcard .avatar-ok').show();
+
+ // Timer
+ $('#vcard .avatar-info').oneTime('10s', function() {
+ $(this).hide();
+ });
+
+ // We put the base64 values in a hidden input to be sent
+ $('#USER-PHOTO-TYPE').val(aType);
+ $('#USER-PHOTO-BINVAL').val(aBinval);
+
+ // We display the avatar !
+ $('#vcard .avatar-container').replaceWith('<div class="avatar-container"><img class="avatar" src="data:' + aType + ';base64,' + aBinval + '" alt="" /></div>');
+ }
+
+ // Any error?
+ else {
+ $('#vcard .avatar-error').show();
+
+ // Timer
+ $('#vcard .avatar-info').oneTime('10s', function() {
+ $(this).hide();
+ });
+
+ logThis('Error while uploading the avatar: ' + dData.find('error').text(), 1);
+ }
+}
+
+// Deletes the encoded avatar of an user
+function deleteAvatar() {
+ // We remove the avatar displayed elements
+ $('#vcard .avatar-info').stopTime();
+ $('#vcard .avatar-info, #vcard .avatar-wait, #vcard .avatar-error, #vcard .avatar-ok, #vcard .avatar-delete').hide();
+ $('#vcard .avatar').remove();
+
+ // We reset the input value
+ $('#USER-PHOTO-TYPE, #USER-PHOTO-BINVAL').val('');
+
+ // We show the avatar-uploading request
+ $('#vcard .no-avatar').show();
+
+ return false;
+}
+
+// Creates a special vCard input
+function createInputVCard(id, type) {
+ // Generate the new ID
+ id = 'USER-' + id;
+
+ // Can append the content
+ if((type == 'user') && !exists('#vcard #' + id))
+ $('#vcard .content').append('<input id="' + id + '" class="vcard-item" type="hidden" />');
+}
+
+// Gets the vCard of a XID
+function getVCard(to, type) {
+ // Generate a special ID
+ var id = genID();
+
+ // New IQ
+ var iq = new JSJaCIQ();
+ iq.setID(id);
+ iq.setType('get');
+ iq.appendNode('vCard', {'xmlns': NS_VCARD});
+
+ // Send the IQ to the good user
+ if(type == 'user') {
+ // Show the wait icon
+ $('#vcard .wait').show();
+
+ // Apply the session ID
+ $('#vcard').attr('data-vcard', id);
+
+ // Send the IQ
+ con.send(iq, handeUVCard);
+ }
+
+ else {
+ // Show the wait icon
+ $('#userinfos .wait').show();
+
+ // Apply the session ID
+ $('#userinfos').attr('data-vcard', id);
+
+ // Send the IQ
+ iq.setTo(to);
+ con.send(iq, handeBVCard);
+ }
+}
+
+// Handles the current connected user's vCard
+function handeUVCard(iq) {
+ handleVCard(iq, 'user');
+}
+
+// Handles an external buddy vCard
+function handeBVCard(iq) {
+ handleVCard(iq, 'buddy');
+}
+
+// Handles a vCard stanza
+function handleVCard(iq, type) {
+ // Extract the data
+ var iqID = iq.getID();
+ var iqFrom = fullXID(getStanzaFrom(iq));
+ var iqNode = iq.getNode();
+
+ // Define some paths
+ var path_vCard = '#vcard[data-vcard=' + iqID + ']';
+ var path_userInfos = '#userinfos[data-vcard=' + iqID + ']';
+
+ // End if the session does not exist
+ if(((type == 'user') && !exists(path_vCard)) || ((type == 'buddy') && !exists(path_userInfos)))
+ return;
+
+ // We retrieve main values
+ var values_yet = [];
+
+ $(iqNode).find('vCard').children().each(function() {
+ // Read the current parent node name
+ var tokenname = (this).nodeName.toUpperCase();
+
+ // Node with a parent
+ if($(this).children().size()) {
+ $(this).children().each(function() {
+ // Get the node values
+ var currentID = tokenname + '-' + (this).nodeName.toUpperCase();
+ var currentText = $(this).text();
+
+ // Not yet added?
+ if(!existArrayValue(values_yet, currentID)) {
+ // Create an input if it does not exist
+ createInputVCard(values_yet, type);
+
+ // Userinfos viewer popup
+ if((type == 'buddy') && currentText) {
+ if(currentID == 'EMAIL-USERID')
+ $(path_userInfos + ' #BUDDY-' + currentID).html('<a href="mailto:' + currentText.htmlEnc() + '" target="_blank">' + currentText.htmlEnc() + '</a>');
+ else
+ $(path_userInfos + ' #BUDDY-' + currentID).text(currentText.htmlEnc());
+ }
+
+ // Profile editor popup
+ else if(type == 'user')
+ $(path_vCard + ' #USER-' + currentID).val(currentText);
+
+ // Avoid duplicating the value
+ values_yet.push(currentID);
+ }
+ });
+ }
+
+ // Node without any parent
+ else {
+ // Get the node values
+ var currentText = $(this).text();
+
+ // Not yet added?
+ if(!existArrayValue(values_yet, tokenname)) {
+ // Create an input if it does not exist
+ createInputVCard(tokenname, type);
+
+ // Userinfos viewer popup
+ if((type == 'buddy') && currentText) {
+ // URL modification
+ if(tokenname == 'URL') {
+ // No http:// or https:// prefix, we should add it
+ if(!currentText.match(/^https?:\/\/(.+)/))
+ currentText = 'http://' + currentText;
+
+ currentText = '<a href="' + currentText + '" target="_blank">' + currentText.htmlEnc() + '</a>';
+ }
+
+ // Description modification
+ else if(tokenname == 'DESC')
+ currentText = filterThisMessage(currentText, getBuddyName(iqFrom).htmlEnc(), true);
+
+ // Other stuffs
+ else
+ currentText = currentText.htmlEnc();
+
+ $(path_userInfos + ' #BUDDY-' + tokenname).html(currentText);
+ }
+
+ // Profile editor popup
+ else if(type == 'user')
+ $(path_vCard + ' #USER-' + tokenname).val(currentText);
+
+ // Avoid duplicating the value
+ values_yet.push(tokenname);
+ }
+ }
+ });
+
+ // Update the stored avatar
+ if(type == 'buddy') {
+ // Get the avatar XML
+ var xml = getPersistent('avatar', iqFrom);
+
+ // If there were no stored avatar previously
+ if($(XMLFromString(xml)).find('type').text() == 'none') {
+ xml = xml.replace(/<forced>false<\/forced>/gi, '<forced>true</forced>');
+ setPersistent('avatar', iqFrom, xml);
+ }
+
+ // Handle the user avatar
+ handleAvatar(iq);
+ }
+
+ // The avatar values targets
+ var aBinval, aType, aContainer;
+
+ if(type == 'user') {
+ aBinval = $('#USER-PHOTO-BINVAL').val();
+ aType = $('#USER-PHOTO-TYPE').val();
+ aContainer = path_vCard + ' .avatar-container';
+ }
+
+ else {
+ aBinval = $(iqNode).find('BINVAL:first').text();
+ aType = $(iqNode).find('TYPE:first').text();
+ aContainer = path_userInfos + ' .avatar-container';
+ }
+
+ // We display the avatar if retrieved
+ if(aBinval) {
+ // No type?
+ if(!aType)
+ aType = 'image/png';
+
+ if(type == 'user') {
+ // We move all the things that we don't need in that case
+ $(path_vCard + ' .no-avatar').hide();
+ $(path_vCard + ' .avatar-delete').show();
+ $(path_vCard + ' .avatar').remove();
+ }
+
+ // We display the avatar we have just received
+ $(aContainer).replaceWith('<div class="avatar-container"><img class="avatar" src="data:' + aType + ';base64,' + aBinval + '" alt="" /></div>');
+ }
+
+ else if(type == 'buddy')
+ $(aContainer).replaceWith('<div class="avatar-container"><img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" /></div>');
+
+ // Do someting depending of the type
+ if(type == 'user') {
+ $(path_vCard + ' .wait').hide();
+ $(path_vCard + ' .finish:first').removeClass('disabled');
+ }
+
+ else
+ vCardBuddyInfos();
+
+ logThis('vCard received: ' + iqFrom);
+}
+
+// Sends the vCard of the user
+function sendVCard() {
+ // Initialize the IQ
+ var iq = new JSJaCIQ();
+ iq.setType('set');
+
+ var vCard = iq.appendNode('vCard', {'xmlns': NS_VCARD});
+
+ // We send the identity part of the form
+ $('#vcard .vcard-item').each(function() {
+ var itemID = $(this).attr('id').replace(/^USER-(.+)/, '$1');
+ var itemVal = $(this).val();
+
+ if(itemVal && itemID) {
+ if(itemID.indexOf('-') != -1) {
+ var tagname = explodeThis('-', itemID, 0);
+ var aNode;
+
+ if(vCard.getElementsByTagName(tagname).length > 0)
+ aNode = vCard.getElementsByTagName(tagname).item(0);
+ else
+ aNode = vCard.appendChild(iq.buildNode(tagname, {'xmlns': NS_VCARD}));
+
+ aNode.appendChild(iq.buildNode(explodeThis('-', itemID, 1), {'xmlns': NS_VCARD}, itemVal));
+ }
+
+ else
+ vCard.appendChild(iq.buildNode(itemID, {'xmlns': NS_VCARD}, itemVal));
+ }
+ });
+
+ // Send the IQ
+ con.send(iq);
+
+ // Send the user nickname & avatar over PEP
+ if(enabledPEP()) {
+ // Read values
+ var user_nick = $('#USER-NICKNAME').val();
+ var photo_bin = $('#USER-PHOTO-BINVAL').val();
+ var photo_type = $('#USER-PHOTO-TYPE').val();
+ var photo_data = Base64.decode(photo_bin) || '';
+ var photo_bytes = photo_data.length || '';
+ var photo_id = hex_sha1(photo_data) || '';
+
+ // Values array
+ var read = [
+ user_nick,
+ photo_bin,
+ [photo_type, photo_id, photo_bytes]
+ ];
+
+ // Nodes array
+ var node = [
+ NS_NICK,
+ NS_URN_ADATA,
+ NS_URN_AMETA
+ ];
+
+ // Generate the XML
+ for(i in read) {
+ var iq = new JSJaCIQ();
+ iq.setType('set');
+
+ var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
+ var publish = pubsub.appendChild(iq.buildNode('publish', {'node': node[i], 'xmlns': NS_PUBSUB}));
+
+ if((i == 0) && read[0]) {
+ var item = publish.appendChild(iq.buildNode('item', {'xmlns': NS_PUBSUB}));
+
+ // Nickname element
+ item.appendChild(iq.buildNode('nick', {'xmlns': NS_NICK}, read[i]));
+ }
+
+ else if(((i == 1) || (i == 2)) && read[1]) {
+ var item = publish.appendChild(iq.buildNode('item', {'xmlns': NS_PUBSUB}));
+
+ // Apply the SHA-1 hash
+ if(photo_id)
+ item.setAttribute('id', photo_id);
+
+ // Avatar data node
+ if(i == 1)
+ item.appendChild(iq.buildNode('data', {'xmlns': NS_URN_ADATA}, read[i]));
+
+ // Avatar metadata node
+ else {
+ var metadata = item.appendChild(iq.buildNode('metadata', {'xmlns': NS_URN_AMETA}));
+
+ if(read[i]) {
+ var meta_info = metadata.appendChild(iq.buildNode('info', {'xmlns': NS_URN_AMETA}));
+
+ if(read[i][0])
+ meta_info.setAttribute('type', read[i][0]);
+ if(read[i][1])
+ meta_info.setAttribute('id', read[i][1]);
+ if(read[i][2])
+ meta_info.setAttribute('bytes', read[i][2]);
+ }
+ }
+ }
+
+ con.send(iq);
+ }
+ }
+
+ // Close the vCard stuffs
+ closeVCard();
+
+ // Get our new avatar
+ getAvatar(getXID(), 'force', 'true', 'forget');
+
+ logThis('vCard sent.');
+
+ return false;
+}
+
+// Plugin launcher
+function launchVCard() {
+ // Focus on the first input
+ $(document).oneTime(10, function() {
+ $('#vcard input:first').focus();
+ });
+
+ // Keyboard events
+ $('#vcard input[type=text]').keyup(function(e) {
+ // Enter pressed: send the vCard
+ if((e.keyCode == 13) && !$('#vcard .finish.save').hasClass('disabled'))
+ return sendVCard();
+ });
+
+ // Click events
+ $('#vcard .tab a').click(function() {
+ // Yet active?
+ if($(this).hasClass('tab-active'))
+ return false;
+
+ // Switch to the good tab
+ var key = parseInt($(this).attr('data-key'));
+
+ return switchVCard(key);
+ });
+
+ $('#vcard .avatar-delete').click(function() {
+ return deleteAvatar();
+ });
+
+ $('#vcard .bottom .finish').click(function() {
+ if($(this).is('.cancel'))
+ return closeVCard();
+ if($(this).is('.save') && !$(this).hasClass('disabled'))
+ return sendVCard();
+
+ return false;
+ });
+
+ // Avatar upload vars
+ var avatar_options = {
+ dataType: 'xml',
+ beforeSubmit: waitAvatarUpload,
+ success: handleAvatarUpload
+ };
+
+ // Avatar upload form submit event
+ $('#vcard-avatar').submit(function() {
+ if($('#vcard .wait').is(':hidden') && $('#vcard .avatar-info.avatar-wait').is(':hidden') && $('#vcard-avatar input[type=file]').val())
+ $(this).ajaxSubmit(avatar_options);
+
+ return false;
+ });
+
+ // Avatar upload input change event
+ $('#vcard-avatar input[type=file]').change(function() {
+ if($('#vcard .wait').is(':hidden') && $('#vcard .avatar-info.avatar-wait').is(':hidden') && $(this).val())
+ $('#vcard-avatar').ajaxSubmit(avatar_options);
+
+ return false;
+ });
+}