From 1df6f5912754b83a65a86a402fabb79c12736bed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B6=C3=9Fl?= Date: Mon, 9 Apr 2012 09:42:14 +0000 Subject: [PATCH 01/96] Use batch requests when syncing friends; this reduces the time for synchronization a lot --- facebook/facebook.php | 2875 +++++++++++++++++++++-------------------- 1 file changed, 1445 insertions(+), 1430 deletions(-) mode change 100755 => 100644 facebook/facebook.php diff --git a/facebook/facebook.php b/facebook/facebook.php old mode 100755 new mode 100644 index 70a353dd..0c31da1f --- a/facebook/facebook.php +++ b/facebook/facebook.php @@ -15,38 +15,38 @@ * in the images directory and may be uploaded as a Facebook app icon. * Use images/friendica-16.jpg for the Icon and images/friendica-128.jpg for the Logo. * b. The url should be your site URL with a trailing slash. - * Friendica is a software application and does not require a Privacy Policy + * Friendica is a software application and does not require a Privacy Policy * or Terms of Service, though your installation of it might. Facebook may require - * that you provide a Privacy Policy, which we find ironic. + * that you provide a Privacy Policy, which we find ironic. * c. Set the following values in your .htconfig.php file * $a->config['facebook']['appid'] = 'xxxxxxxxxxx'; * $a->config['facebook']['appsecret'] = 'xxxxxxxxxxxxxxx'; * Replace with the settings Facebook gives you. - * d. Navigate to Set Web->Site URL & Domain -> Website Settings. Set - * Site URL to yoursubdomain.yourdomain.com. Set Site Domain to your + * d. Navigate to Set Web->Site URL & Domain -> Website Settings. Set + * Site URL to yoursubdomain.yourdomain.com. Set Site Domain to your * yourdomain.com. * 2. Visit the Facebook Settings section of the "Settings->Plugin Settings" page. * and click 'Install Facebook Connector'. * 3. Visit the Facebook Settings section of the "Settings->Plugin Settings" page. * and click 'Install Facebook Connector'. - * 4. This will ask you to login to Facebook and grant permission to the - * plugin to do its stuff. Allow it to do so. + * 4. This will ask you to login to Facebook and grant permission to the + * plugin to do its stuff. Allow it to do so. * 5. Optional step: If you want to use Facebook Real Time Updates (so new messages * and new contacts are added ~1min after they are postet / added on FB), go to * Settings -> plugins -> facebook and press the "Activate Real-Time Updates"-button. * 6. You're done. To turn it off visit the Plugin Settings page again and * 'Remove Facebook posting'. * - * Vidoes and embeds will not be posted if there is no other content. Links - * and images will be converted to a format suitable for the Facebook API and - * long posts truncated - with a link to view the full post. + * Vidoes and embeds will not be posted if there is no other content. Links + * and images will be converted to a format suitable for the Facebook API and + * long posts truncated - with a link to view the full post. * * Facebook contacts will not be able to view private photos, as they are not able to - * authenticate to your site to establish identity. We will address this + * authenticate to your site to establish identity. We will address this * in a future release. */ - - /** TODO + +/** TODO * - Implement a method for the administrator to delete all configuration data the plugin has created, * e.g. the app_access_token */ @@ -61,28 +61,28 @@ define('FACEBOOK_MIN_POLL_INTERVAL', 5); function facebook_install() { - register_hook('post_local', 'addon/facebook/facebook.php', 'facebook_post_local'); - register_hook('notifier_normal', 'addon/facebook/facebook.php', 'facebook_post_hook'); - register_hook('jot_networks', 'addon/facebook/facebook.php', 'facebook_jot_nets'); - register_hook('connector_settings', 'addon/facebook/facebook.php', 'facebook_plugin_settings'); - register_hook('cron', 'addon/facebook/facebook.php', 'facebook_cron'); - register_hook('enotify', 'addon/facebook/facebook.php', 'facebook_enotify'); - register_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook'); + register_hook('post_local', 'addon/facebook/facebook.php', 'facebook_post_local'); + register_hook('notifier_normal', 'addon/facebook/facebook.php', 'facebook_post_hook'); + register_hook('jot_networks', 'addon/facebook/facebook.php', 'facebook_jot_nets'); + register_hook('connector_settings', 'addon/facebook/facebook.php', 'facebook_plugin_settings'); + register_hook('cron', 'addon/facebook/facebook.php', 'facebook_cron'); + register_hook('enotify', 'addon/facebook/facebook.php', 'facebook_enotify'); + register_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook'); } function facebook_uninstall() { - unregister_hook('post_local', 'addon/facebook/facebook.php', 'facebook_post_local'); - unregister_hook('notifier_normal', 'addon/facebook/facebook.php', 'facebook_post_hook'); - unregister_hook('jot_networks', 'addon/facebook/facebook.php', 'facebook_jot_nets'); - unregister_hook('connector_settings', 'addon/facebook/facebook.php', 'facebook_plugin_settings'); - unregister_hook('cron', 'addon/facebook/facebook.php', 'facebook_cron'); - unregister_hook('enotify', 'addon/facebook/facebook.php', 'facebook_enotify'); - unregister_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook'); + unregister_hook('post_local', 'addon/facebook/facebook.php', 'facebook_post_local'); + unregister_hook('notifier_normal', 'addon/facebook/facebook.php', 'facebook_post_hook'); + unregister_hook('jot_networks', 'addon/facebook/facebook.php', 'facebook_jot_nets'); + unregister_hook('connector_settings', 'addon/facebook/facebook.php', 'facebook_plugin_settings'); + unregister_hook('cron', 'addon/facebook/facebook.php', 'facebook_cron'); + unregister_hook('enotify', 'addon/facebook/facebook.php', 'facebook_enotify'); + unregister_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook'); - // hook moved - unregister_hook('post_local_end', 'addon/facebook/facebook.php', 'facebook_post_hook'); - unregister_hook('plugin_settings', 'addon/facebook/facebook.php', 'facebook_plugin_settings'); + // hook moved + unregister_hook('post_local_end', 'addon/facebook/facebook.php', 'facebook_post_hook'); + unregister_hook('plugin_settings', 'addon/facebook/facebook.php', 'facebook_plugin_settings'); } @@ -96,199 +96,195 @@ function facebook_module() {} // If $_REQUEST["realtime_cb"] is set, this is a callback from the Real-Time Updates API function facebook_init(&$a) { - - if (x($_REQUEST, "realtime_cb") && x($_REQUEST, "realtime_cb")) { - logger("facebook_init: Facebook Real-Time callback called", LOGGER_DEBUG); - - if (x($_REQUEST, "hub_verify_token")) { - // this is the verification callback while registering for real time updates - - $verify_token = get_config('facebook', 'cb_verify_token'); - if ($verify_token != $_REQUEST["hub_verify_token"]) { - logger('facebook_init: Wrong Facebook Callback Verifier - expected ' . $verify_token . ', got ' . $_REQUEST["hub_verify_token"]); - return; - } - - if (x($_REQUEST, "hub_challenge")) { - logger('facebook_init: Answering Challenge: ' . $_REQUEST["hub_challenge"], LOGGER_DATA); - echo $_REQUEST["hub_challenge"]; - die(); - } - } - - require_once('include/items.php'); - - // this is a status update - $content = file_get_contents("php://input"); - if (is_numeric($content)) $content = file_get_contents("php://input"); - $js = json_decode($content); - logger(print_r($js, true), LOGGER_DATA); - - if (!isset($js->object) || $js->object != "user" || !isset($js->entry)) { - logger('facebook_init: Could not parse Real-Time Update data', LOGGER_DEBUG); - return; - } - - $affected_users = array("feed" => array(), "friends" => array()); - - foreach ($js->entry as $entry) { - $fbuser = $entry->uid; - foreach ($entry->changed_fields as $field) { - if (!isset($affected_users[$field])) { - logger('facebook_init: Unknown field "' . $field . '"'); - continue; - } - if (in_array($fbuser, $affected_users[$field])) continue; - - $r = q("SELECT `uid` FROM `pconfig` WHERE `cat` = 'facebook' AND `k` = 'self_id' AND `v` = '%s' LIMIT 1", dbesc($fbuser)); - if(! count($r)) - continue; - $uid = $r[0]['uid']; - - $access_token = get_pconfig($uid,'facebook','access_token'); - if(! $access_token) - return; - - switch ($field) { - case "feed": - logger('facebook_init: FB-User ' . $fbuser . ' / feed', LOGGER_DEBUG); - - if(! get_pconfig($uid,'facebook','no_wall')) { - $private_wall = intval(get_pconfig($uid,'facebook','private_wall')); - $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token); - if($s) { - $j = json_decode($s); - if (isset($j->data)) { - logger('facebook_init: wall: ' . print_r($j,true), LOGGER_DATA); - fb_consume_stream($uid,$j,($private_wall) ? false : true); - } else { - logger('facebook_init: wall: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL); - } - } - } - - break; - case "friends": - logger('facebook_init: FB-User ' . $fbuser . ' / friends', LOGGER_DEBUG); - - fb_get_friends($uid, false); - set_pconfig($uid,'facebook','friend_check',time()); - break; - default: - logger('facebook_init: Unknown callback field for ' . $fbuser, LOGGER_NORMAL); - } - $affected_users[$field][] = $fbuser; - } - } - } - - if($a->argc != 2) - return; - $nick = $a->argv[1]; - if(strlen($nick)) - $r = q("SELECT `uid` FROM `user` WHERE `nickname` = '%s' LIMIT 1", - dbesc($nick) - ); - if(! count($r)) - return; + if (x($_REQUEST, "realtime_cb") && x($_REQUEST, "realtime_cb")) { + logger("facebook_init: Facebook Real-Time callback called", LOGGER_DEBUG); - $uid = $r[0]['uid']; - $auth_code = (x($_GET, 'code') ? $_GET['code'] : ''); - $error = (x($_GET, 'error_description') ? $_GET['error_description'] : ''); + if (x($_REQUEST, "hub_verify_token")) { + // this is the verification callback while registering for real time updates + + $verify_token = get_config('facebook', 'cb_verify_token'); + if ($verify_token != $_REQUEST["hub_verify_token"]) { + logger('facebook_init: Wrong Facebook Callback Verifier - expected ' . $verify_token . ', got ' . $_REQUEST["hub_verify_token"]); + return; + } + + if (x($_REQUEST, "hub_challenge")) { + logger('facebook_init: Answering Challenge: ' . $_REQUEST["hub_challenge"], LOGGER_DATA); + echo $_REQUEST["hub_challenge"]; + die(); + } + } + + require_once('include/items.php'); + + // this is a status update + $content = file_get_contents("php://input"); + if (is_numeric($content)) $content = file_get_contents("php://input"); + $js = json_decode($content); + logger(print_r($js, true), LOGGER_DATA); + + if (!isset($js->object) || $js->object != "user" || !isset($js->entry)) { + logger('facebook_init: Could not parse Real-Time Update data', LOGGER_DEBUG); + return; + } + + $affected_users = array("feed" => array(), "friends" => array()); + + foreach ($js->entry as $entry) { + $fbuser = $entry->uid; + foreach ($entry->changed_fields as $field) { + if (!isset($affected_users[$field])) { + logger('facebook_init: Unknown field "' . $field . '"'); + continue; + } + if (in_array($fbuser, $affected_users[$field])) continue; + + $r = q("SELECT `uid` FROM `pconfig` WHERE `cat` = 'facebook' AND `k` = 'self_id' AND `v` = '%s' LIMIT 1", dbesc($fbuser)); + if(! count($r)) + continue; + $uid = $r[0]['uid']; + + $access_token = get_pconfig($uid,'facebook','access_token'); + if(! $access_token) + return; + + switch ($field) { + case "feed": + logger('facebook_init: FB-User ' . $fbuser . ' / feed', LOGGER_DEBUG); + + if(! get_pconfig($uid,'facebook','no_wall')) { + $private_wall = intval(get_pconfig($uid,'facebook','private_wall')); + $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token); + if($s) { + $j = json_decode($s); + if (isset($j->data)) { + logger('facebook_init: wall: ' . print_r($j,true), LOGGER_DATA); + fb_consume_stream($uid,$j,($private_wall) ? false : true); + } else { + logger('facebook_init: wall: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL); + } + } + } + + break; + case "friends": + logger('facebook_init: FB-User ' . $fbuser . ' / friends', LOGGER_DEBUG); + + fb_get_friends($uid, false); + set_pconfig($uid,'facebook','friend_check',time()); + break; + default: + logger('facebook_init: Unknown callback field for ' . $fbuser, LOGGER_NORMAL); + } + $affected_users[$field][] = $fbuser; + } + } + } - if($error) - logger('facebook_init: Error: ' . $error); + if($a->argc != 2) + return; + $nick = $a->argv[1]; + if(strlen($nick)) + $r = q("SELECT `uid` FROM `user` WHERE `nickname` = '%s' LIMIT 1", + dbesc($nick) + ); + if(! count($r)) + return; - if($auth_code && $uid) { + $uid = $r[0]['uid']; + $auth_code = (x($_GET, 'code') ? $_GET['code'] : ''); + $error = (x($_GET, 'error_description') ? $_GET['error_description'] : ''); - $appid = get_config('facebook','appid'); - $appsecret = get_config('facebook', 'appsecret'); + if($error) + logger('facebook_init: Error: ' . $error); - $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id=' - . $appid . '&client_secret=' . $appsecret . '&redirect_uri=' - . urlencode($a->get_baseurl() . '/facebook/' . $nick) - . '&code=' . $auth_code); + if($auth_code && $uid) { - logger('facebook_init: returned access token: ' . $x, LOGGER_DATA); + $appid = get_config('facebook','appid'); + $appsecret = get_config('facebook', 'appsecret'); - if(strpos($x,'access_token=') !== false) { - $token = str_replace('access_token=', '', $x); - if(strpos($token,'&') !== false) - $token = substr($token,0,strpos($token,'&')); - set_pconfig($uid,'facebook','access_token',$token); - set_pconfig($uid,'facebook','post','1'); - if(get_pconfig($uid,'facebook','no_linking') === false) - set_pconfig($uid,'facebook','no_linking',1); - fb_get_self($uid); - fb_get_friends($uid, true); - fb_consume_all($uid); + $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id=' + . $appid . '&client_secret=' . $appsecret . '&redirect_uri=' + . urlencode($a->get_baseurl() . '/facebook/' . $nick) + . '&code=' . $auth_code); - } + logger('facebook_init: returned access token: ' . $x, LOGGER_DATA); - } + if(strpos($x,'access_token=') !== false) { + $token = str_replace('access_token=', '', $x); + if(strpos($token,'&') !== false) + $token = substr($token,0,strpos($token,'&')); + set_pconfig($uid,'facebook','access_token',$token); + set_pconfig($uid,'facebook','post','1'); + if(get_pconfig($uid,'facebook','no_linking') === false) + set_pconfig($uid,'facebook','no_linking',1); + fb_get_self($uid); + fb_get_friends($uid, true); + fb_consume_all($uid); + + } + + } } function fb_get_self($uid) { - $access_token = get_pconfig($uid,'facebook','access_token'); - if(! $access_token) - return; - $s = fetch_url('https://graph.facebook.com/me/?access_token=' . $access_token); - if($s) { - $j = json_decode($s); - set_pconfig($uid,'facebook','self_id',(string) $j->id); - } + $access_token = get_pconfig($uid,'facebook','access_token'); + if(! $access_token) + return; + $s = fetch_url('https://graph.facebook.com/me/?access_token=' . $access_token); + if($s) { + $j = json_decode($s); + set_pconfig($uid,'facebook','self_id',(string) $j->id); + } } -function fb_get_friends_sync_new($uid, $access_token, $person) { - $link = 'http://facebook.com/profile.php?id=' . $person->id; - - $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' LIMIT 1", - intval($uid), - dbesc($link) - ); - - if (count($r) == 0) { - logger('fb_get_friends: new contact found: ' . $link, LOGGER_DEBUG); - - fb_get_friends_sync_full($uid, $access_token, $person); - } +function fb_get_friends_sync_new($uid, $access_token, $persons) { + $persons_todo = array(); + foreach ($persons as $person) { + $link = 'http://facebook.com/profile.php?id=' . $person->id; + + $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' LIMIT 1", + intval($uid), + dbesc($link) + ); + + if (count($r) == 0) { + logger('fb_get_friends: new contact found: ' . $link, LOGGER_DEBUG); + $persons_todo[] = $person; + } + + if (count($persons_todo) > 0) fb_get_friends_sync_full($uid, $access_token, $persons_todo); + } } -function fb_get_friends_sync_full($uid, $access_token, $person) { - $s = fetch_url('https://graph.facebook.com/' . $person->id . '?access_token=' . $access_token); - if($s) { - $jp = json_decode($s); - logger('fb_get_friends: info: ' . print_r($jp,true), LOGGER_DATA); +function fb_get_friends_sync_parsecontact($uid, $contact) { + $contact->link = 'http://facebook.com/profile.php?id=' . $contact->id; - // always use numeric link for consistency + // If its a page then set the first name from the username + if (!$contact->first_name and $contact->username) + $contact->first_name = $contact->username; - $jp->link = 'http://facebook.com/profile.php?id=' . $person->id; + // check if we already have a contact - // If its a page then set the first name from the username - if (!$jp->first_name and $jp->username) - $jp->first_name = $jp->username; + $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' LIMIT 1", + intval($uid), + dbesc($contact->link) + ); - // check if we already have a contact + if(count($r)) { - $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' LIMIT 1", - intval($uid), - dbesc($jp->link) - ); + // check that we have all the photos, this has been known to fail on occasion - if(count($r)) { + if((! $r[0]['photo']) || (! $r[0]['thumb']) || (! $r[0]['micro'])) { + require_once("Photo.php"); - // check that we have all the photos, this has been known to fail on occasion + $photos = import_profile_photo('https://graph.facebook.com/' . $contact->id . '/picture', $uid, $r[0]['id']); - if((! $r[0]['photo']) || (! $r[0]['thumb']) || (! $r[0]['micro'])) { - require_once("Photo.php"); - - $photos = import_profile_photo('https://graph.facebook.com/' . $jp->id . '/picture', $uid, $r[0]['id']); - - $r = q("UPDATE `contact` SET `photo` = '%s', + $r = q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s', `name-date` = '%s', @@ -296,59 +292,59 @@ function fb_get_friends_sync_full($uid, $access_token, $person) { `avatar-date` = '%s' WHERE `id` = %d LIMIT 1 ", - dbesc($photos[0]), - dbesc($photos[1]), - dbesc($photos[2]), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - intval($r[0]['id']) - ); - } - return; - } - else { + dbesc($photos[0]), + dbesc($photos[1]), + dbesc($photos[2]), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + intval($r[0]['id']) + ); + } + return; + } + else { - // create contact record - $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`, + // create contact record + $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`, `name`, `nick`, `photo`, `network`, `rel`, `priority`, `writable`, `blocked`, `readonly`, `pending` ) VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, 0, 0, 0 ) ", - intval($uid), - dbesc(datetime_convert()), - dbesc($jp->link), - dbesc(normalise_link($jp->link)), - dbesc(''), - dbesc(''), - dbesc($jp->id), - dbesc('facebook ' . $jp->id), - dbesc($jp->name), - dbesc(($jp->nickname) ? $jp->nickname : strtolower($jp->first_name)), - dbesc('https://graph.facebook.com/' . $jp->id . '/picture'), - dbesc(NETWORK_FACEBOOK), - intval(CONTACT_IS_FRIEND), - intval(1), - intval(1) - ); - } + intval($uid), + dbesc(datetime_convert()), + dbesc($contact->link), + dbesc(normalise_link($contact->link)), + dbesc(''), + dbesc(''), + dbesc($contact->id), + dbesc('facebook ' . $contact->id), + dbesc($contact->name), + dbesc(($contact->nickname) ? $contact->nickname : strtolower($contact->first_name)), + dbesc('https://graph.facebook.com/' . $contact->id . '/picture'), + dbesc(NETWORK_FACEBOOK), + intval(CONTACT_IS_FRIEND), + intval(1), + intval(1) + ); + } - $r = q("SELECT * FROM `contact` WHERE `url` = '%s' AND `uid` = %d LIMIT 1", - dbesc($jp->link), - intval($uid) - ); + $r = q("SELECT * FROM `contact` WHERE `url` = '%s' AND `uid` = %d LIMIT 1", + dbesc($contact->link), + intval($uid) + ); - if(! count($r)) { - return; - } + if(! count($r)) { + return; + } - $contact = $r[0]; - $contact_id = $r[0]['id']; + $contact = $r[0]; + $contact_id = $r[0]['id']; - require_once("Photo.php"); + require_once("Photo.php"); - $photos = import_profile_photo($r[0]['photo'],$uid,$contact_id); + $photos = import_profile_photo($r[0]['photo'],$uid,$contact_id); - $r = q("UPDATE `contact` SET `photo` = '%s', + $r = q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s', `name-date` = '%s', @@ -356,49 +352,68 @@ function fb_get_friends_sync_full($uid, $access_token, $person) { `avatar-date` = '%s' WHERE `id` = %d LIMIT 1 ", - dbesc($photos[0]), - dbesc($photos[1]), - dbesc($photos[2]), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - intval($contact_id) - ); + dbesc($photos[0]), + dbesc($photos[1]), + dbesc($photos[2]), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + intval($contact_id) + ); +} - } +function fb_get_friends_sync_full($uid, $access_token, $persons) { + if (count($persons) == 0) return; + $nums = Ceil(count($persons) / 50); + for ($i = 0; $i < $nums; $i++) { + $batch_request = array(); + for ($j = $i * 50; $j < ($i+1) * 50 && $j < count($persons); $j++) $batch_request[] = array('method'=>'GET', 'relative_url'=>$persons[$j]->id); + $s = post_url('https://graph.facebook.com/', array('access_token' => $access_token, 'batch' => json_encode($batch_request))); + if($s) { + $results = json_decode($s); + logger('fb_get_friends: info: ' . print_r($results,true), LOGGER_DATA); + foreach ($results as $contact) { + if ($contact->code != 200) logger('fb_get_friends: not found: ' . print_r($contact,true), LOGGER_DEBUG); + else fb_get_friends_sync_parsecontact($uid, json_decode($contact->body)); + } + } + } } // if $fullsync is true, only new contacts are searched for function fb_get_friends($uid, $fullsync = true) { - $r = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1", - intval($uid) - ); - if(! count($r)) - return; + $r = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1", + intval($uid) + ); + if(! count($r)) + return; - $access_token = get_pconfig($uid,'facebook','access_token'); + $access_token = get_pconfig($uid,'facebook','access_token'); - $no_linking = get_pconfig($uid,'facebook','no_linking'); - if($no_linking) - return; + $no_linking = get_pconfig($uid,'facebook','no_linking'); + if($no_linking) + return; - if(! $access_token) - return; - $s = fetch_url('https://graph.facebook.com/me/friends?access_token=' . $access_token); - if($s) { - logger('facebook: fb_get_friends: ' . $s, LOGGER_DATA); - $j = json_decode($s); - logger('facebook: fb_get_friends: json: ' . print_r($j,true), LOGGER_DATA); - if(! $j->data) - return; - foreach($j->data as $person) - if ($fullsync) - fb_get_friends_sync_full($uid, $access_token, $person); - else - fb_get_friends_sync_new($uid, $access_token, $person); - } + if(! $access_token) + return; + $s = fetch_url('https://graph.facebook.com/me/friends?access_token=' . $access_token); + if($s) { + logger('facebook: fb_get_friends: ' . $s, LOGGER_DATA); + $j = json_decode($s); + logger('facebook: fb_get_friends: json: ' . print_r($j,true), LOGGER_DATA); + if(! $j->data) + return; + + $persons_todo = array(); + foreach($j->data as $person) $persons_todo[] = $person; + + if ($fullsync) + fb_get_friends_sync_full($uid, $access_token, $persons_todo); + else + fb_get_friends_sync_new($uid, $access_token, $persons_todo); + } } // This is the POST method to the facebook settings page @@ -406,232 +421,232 @@ function fb_get_friends($uid, $fullsync = true) { function facebook_post(&$a) { - $uid = local_user(); - if($uid){ + $uid = local_user(); + if($uid){ - $value = ((x($_POST,'post_by_default')) ? intval($_POST['post_by_default']) : 0); - set_pconfig($uid,'facebook','post_by_default', $value); + $value = ((x($_POST,'post_by_default')) ? intval($_POST['post_by_default']) : 0); + set_pconfig($uid,'facebook','post_by_default', $value); - $no_linking = get_pconfig($uid,'facebook','no_linking'); + $no_linking = get_pconfig($uid,'facebook','no_linking'); - $no_wall = ((x($_POST,'facebook_no_wall')) ? intval($_POST['facebook_no_wall']) : 0); - set_pconfig($uid,'facebook','no_wall',$no_wall); + $no_wall = ((x($_POST,'facebook_no_wall')) ? intval($_POST['facebook_no_wall']) : 0); + set_pconfig($uid,'facebook','no_wall',$no_wall); - $private_wall = ((x($_POST,'facebook_private_wall')) ? intval($_POST['facebook_private_wall']) : 0); - set_pconfig($uid,'facebook','private_wall',$private_wall); - + $private_wall = ((x($_POST,'facebook_private_wall')) ? intval($_POST['facebook_private_wall']) : 0); + set_pconfig($uid,'facebook','private_wall',$private_wall); - set_pconfig($uid,'facebook','blocked_apps',escape_tags(trim($_POST['blocked_apps']))); - $linkvalue = ((x($_POST,'facebook_linking')) ? intval($_POST['facebook_linking']) : 0); - set_pconfig($uid,'facebook','no_linking', (($linkvalue) ? 0 : 1)); + set_pconfig($uid,'facebook','blocked_apps',escape_tags(trim($_POST['blocked_apps']))); - // FB linkage was allowed but has just been turned off - remove all FB contacts and posts + $linkvalue = ((x($_POST,'facebook_linking')) ? intval($_POST['facebook_linking']) : 0); + set_pconfig($uid,'facebook','no_linking', (($linkvalue) ? 0 : 1)); - if((! intval($no_linking)) && (! intval($linkvalue))) { - $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `network` = '%s' ", - intval($uid), - dbesc(NETWORK_FACEBOOK) - ); - if(count($r)) { - require_once('include/Contact.php'); - foreach($r as $rr) - contact_remove($rr['id']); - } - } - elseif(intval($no_linking) && intval($linkvalue)) { - // FB linkage is now allowed - import stuff. - fb_get_self($uid); - fb_get_friends($uid, true); - fb_consume_all($uid); - } + // FB linkage was allowed but has just been turned off - remove all FB contacts and posts - info( t('Settings updated.') . EOL); - } + if((! intval($no_linking)) && (! intval($linkvalue))) { + $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `network` = '%s' ", + intval($uid), + dbesc(NETWORK_FACEBOOK) + ); + if(count($r)) { + require_once('include/Contact.php'); + foreach($r as $rr) + contact_remove($rr['id']); + } + } + elseif(intval($no_linking) && intval($linkvalue)) { + // FB linkage is now allowed - import stuff. + fb_get_self($uid); + fb_get_friends($uid, true); + fb_consume_all($uid); + } - return; + info( t('Settings updated.') . EOL); + } + + return; } // Facebook settings form function facebook_content(&$a) { - if(! local_user()) { - notice( t('Permission denied.') . EOL); - return ''; - } + if(! local_user()) { + notice( t('Permission denied.') . EOL); + return ''; + } - if($a->argc > 1 && $a->argv[1] === 'remove') { - del_pconfig(local_user(),'facebook','post'); - info( t('Facebook disabled') . EOL); - } + if($a->argc > 1 && $a->argv[1] === 'remove') { + del_pconfig(local_user(),'facebook','post'); + info( t('Facebook disabled') . EOL); + } - if($a->argc > 1 && $a->argv[1] === 'friends') { - fb_get_friends(local_user(), true); - info( t('Updating contacts') . EOL); - } + if($a->argc > 1 && $a->argv[1] === 'friends') { + fb_get_friends(local_user(), true); + info( t('Updating contacts') . EOL); + } - $o = ''; - - $fb_installed = false; - if (get_pconfig(local_user(),'facebook','post')) { - $access_token = get_pconfig(local_user(),'facebook','access_token'); - if ($access_token) { - $private_wall = intval(get_pconfig($uid,'facebook','private_wall')); - $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token); - if($s) { - $j = json_decode($s); - if (isset($j->data)) $fb_installed = true; - } - } - } - - $appid = get_config('facebook','appid'); + $o = ''; - if(! $appid) { - notice( t('Facebook API key is missing.') . EOL); - return ''; - } + $fb_installed = false; + if (get_pconfig(local_user(),'facebook','post')) { + $access_token = get_pconfig(local_user(),'facebook','access_token'); + if ($access_token) { + $private_wall = intval(get_pconfig(local_user(),'facebook','private_wall')); + $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token); + if($s) { + $j = json_decode($s); + if (isset($j->data)) $fb_installed = true; + } + } + } - $a->page['htmlhead'] .= '' . "\r\n"; + $appid = get_config('facebook','appid'); - $o .= '

' . t('Facebook Connect') . '

'; + if(! $appid) { + notice( t('Facebook API key is missing.') . EOL); + return ''; + } - if(! $fb_installed) { - $o .= '
'; + $a->page['htmlhead'] .= '' . "\r\n"; - $o .= '' . t('Install Facebook connector for this account.') . ''; - $o .= '
'; - } + $o .= '

' . t('Facebook Connect') . '

'; - if($fb_installed) { - $o .= '
'; + if(! $fb_installed) { + $o .= ''; + $o .= '' . t('Install Facebook connector for this account.') . ''; + $o .= '
'; + } - $o .= '
'; + if($fb_installed) { + $o .= ''; - - $o .= '
'; - $o .= '
'; - $post_by_default = get_pconfig(local_user(),'facebook','post_by_default'); - $checked = (($post_by_default) ? ' checked="checked" ' : ''); - $o .= '' . ' ' . t('Post to Facebook by default') . EOL; + $o .= '' . t('Remove Facebook connector') . '
'; - $no_linking = get_pconfig(local_user(),'facebook','no_linking'); - $checked = (($no_linking) ? '' : ' checked="checked" '); - $o .= '' . ' ' . t('Link all your Facebook friends and conversations on this website') . EOL ; + $o .= '
'; - $o .= '

' . t('Facebook conversations consist of your profile wall and your friend stream.'); - $o .= ' ' . t('On this website, your Facebook friend stream is only visible to you.'); - $o .= ' ' . t('The following settings determine the privacy of your Facebook profile wall on this website.') . '

'; + $o .= '' . t('Re-authenticate [This is necessary whenever your Facebook password is changed.]') . ''; + $o .= '
'; - $private_wall = get_pconfig(local_user(),'facebook','private_wall'); - $checked = (($private_wall) ? ' checked="checked" ' : ''); - $o .= '' . ' ' . t('On this website your Facebook profile wall conversations will only be visible to you') . EOL ; + $o .= '
'; + $o .= ''; + $post_by_default = get_pconfig(local_user(),'facebook','post_by_default'); + $checked = (($post_by_default) ? ' checked="checked" ' : ''); + $o .= '' . ' ' . t('Post to Facebook by default') . EOL; + + $no_linking = get_pconfig(local_user(),'facebook','no_linking'); + $checked = (($no_linking) ? '' : ' checked="checked" '); + $o .= '' . ' ' . t('Link all your Facebook friends and conversations on this website') . EOL ; + + $o .= '

' . t('Facebook conversations consist of your profile wall and your friend stream.'); + $o .= ' ' . t('On this website, your Facebook friend stream is only visible to you.'); + $o .= ' ' . t('The following settings determine the privacy of your Facebook profile wall on this website.') . '

'; + + $private_wall = get_pconfig(local_user(),'facebook','private_wall'); + $checked = (($private_wall) ? ' checked="checked" ' : ''); + $o .= '' . ' ' . t('On this website your Facebook profile wall conversations will only be visible to you') . EOL ; - $no_wall = get_pconfig(local_user(),'facebook','no_wall'); - $checked = (($no_wall) ? ' checked="checked" ' : ''); - $o .= '' . ' ' . t('Do not import your Facebook profile wall conversations') . EOL ; + $no_wall = get_pconfig(local_user(),'facebook','no_wall'); + $checked = (($no_wall) ? ' checked="checked" ' : ''); + $o .= '' . ' ' . t('Do not import your Facebook profile wall conversations') . EOL ; - $o .= '

' . t('If you choose to link conversations and leave both of these boxes unchecked, your Facebook profile wall will be merged with your profile wall on this website and your privacy settings on this website will be used to determine who may see the conversations.') . '

'; + $o .= '

' . t('If you choose to link conversations and leave both of these boxes unchecked, your Facebook profile wall will be merged with your profile wall on this website and your privacy settings on this website will be used to determine who may see the conversations.') . '

'; - $blocked_apps = get_pconfig(local_user(),'facebook','blocked_apps'); + $blocked_apps = get_pconfig(local_user(),'facebook','blocked_apps'); - $o .= '
'; - $o .= '
'; + $o .= '
'; + $o .= '
'; - $o .= '
'; - } + $o .= '
'; + } - return $o; + return $o; } function facebook_cron($a,$b) { - $last = get_config('facebook','last_poll'); - - $poll_interval = intval(get_config('facebook','poll_interval')); - if(! $poll_interval) - $poll_interval = FACEBOOK_DEFAULT_POLL_INTERVAL; + $last = get_config('facebook','last_poll'); - if($last) { - $next = $last + $poll_interval; - if($next > time()) - return; - } + $poll_interval = intval(get_config('facebook','poll_interval')); + if(! $poll_interval) + $poll_interval = FACEBOOK_DEFAULT_POLL_INTERVAL; - logger('facebook_cron'); + if($last) { + $next = $last + $poll_interval; + if($next > time()) + return; + } + + logger('facebook_cron'); - // Find the FB users on this site and randomize in case one of them - // uses an obscene amount of memory. It may kill this queue run - // but hopefully we'll get a few others through on each run. + // Find the FB users on this site and randomize in case one of them + // uses an obscene amount of memory. It may kill this queue run + // but hopefully we'll get a few others through on each run. - $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'facebook' AND `k` = 'post' AND `v` = '1' ORDER BY RAND() "); - if(count($r)) { - foreach($r as $rr) { - if(get_pconfig($rr['uid'],'facebook','no_linking')) - continue; - $ab = intval(get_config('system','account_abandon_days')); - if($ab > 0) { - $z = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `login_date` > UTC_TIMESTAMP() - INTERVAL %d DAY LIMIT 1", - intval($rr['uid']), - intval($ab) - ); - if(! count($z)) - continue; - } + $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'facebook' AND `k` = 'post' AND `v` = '1' ORDER BY RAND() "); + if(count($r)) { + foreach($r as $rr) { + if(get_pconfig($rr['uid'],'facebook','no_linking')) + continue; + $ab = intval(get_config('system','account_abandon_days')); + if($ab > 0) { + $z = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `login_date` > UTC_TIMESTAMP() - INTERVAL %d DAY LIMIT 1", + intval($rr['uid']), + intval($ab) + ); + if(! count($z)) + continue; + } - // check for new friends once a day - $last_friend_check = get_pconfig($rr['uid'],'facebook','friend_check'); - if($last_friend_check) - $next_friend_check = $last_friend_check + 86400; - if($next_friend_check <= time()) { - fb_get_friends($rr['uid'], true); - set_pconfig($rr['uid'],'facebook','friend_check',time()); - } - fb_consume_all($rr['uid']); - } - } - - if (get_config('facebook', 'realtime_active') == 1) { - if (!facebook_check_realtime_active()) { - - logger('facebook_cron: Facebook is not sending Real-Time Updates any more, although it is supposed to. Trying to fix it...', LOGGER_NORMAL); - facebook_subscription_add_users(); - - if (facebook_check_realtime_active()) - logger('facebook_cron: Successful', LOGGER_NORMAL); - else { - logger('facebook_cron: Failed', LOGGER_NORMAL); - - if(strlen($a->config['admin_email']) && !get_config('facebook', 'realtime_err_mailsent')) { - $res = mail($a->config['admin_email'], t('Problems with Facebook Real-Time Updates'), - "Hi!\n\nThere's a problem with the Facebook Real-Time Updates that cannot be solved automatically. Maybe a permission issue?\n\nPlease try to re-activate it on " . $a->config["system"]["url"] . "/admin/plugins/facebook\n\nThis e-mail will only be sent once.", - 'From: ' . t('Administrator') . '@' . $_SERVER['SERVER_NAME'] . "\n" - . 'Content-type: text/plain; charset=UTF-8' . "\n" - . 'Content-transfer-encoding: 8bit' - ); - - set_config('facebook', 'realtime_err_mailsent', 1); - } - } - } else { // !facebook_check_realtime_active() - del_config('facebook', 'realtime_err_mailsent'); - } - } - - set_config('facebook','last_poll', time()); + // check for new friends once a day + $last_friend_check = get_pconfig($rr['uid'],'facebook','friend_check'); + if($last_friend_check) + $next_friend_check = $last_friend_check + 86400; + if($next_friend_check <= time()) { + fb_get_friends($rr['uid'], true); + set_pconfig($rr['uid'],'facebook','friend_check',time()); + } + fb_consume_all($rr['uid']); + } + } + + if (get_config('facebook', 'realtime_active') == 1) { + if (!facebook_check_realtime_active()) { + + logger('facebook_cron: Facebook is not sending Real-Time Updates any more, although it is supposed to. Trying to fix it...', LOGGER_NORMAL); + facebook_subscription_add_users(); + + if (facebook_check_realtime_active()) + logger('facebook_cron: Successful', LOGGER_NORMAL); + else { + logger('facebook_cron: Failed', LOGGER_NORMAL); + + if(strlen($a->config['admin_email']) && !get_config('facebook', 'realtime_err_mailsent')) { + $res = mail($a->config['admin_email'], t('Problems with Facebook Real-Time Updates'), + "Hi!\n\nThere's a problem with the Facebook Real-Time Updates that cannot be solved automatically. Maybe a permission issue?\n\nPlease try to re-activate it on " . $a->config["system"]["url"] . "/admin/plugins/facebook\n\nThis e-mail will only be sent once.", + 'From: ' . t('Administrator') . '@' . $_SERVER['SERVER_NAME'] . "\n" + . 'Content-type: text/plain; charset=UTF-8' . "\n" + . 'Content-transfer-encoding: 8bit' + ); + + set_config('facebook', 'realtime_err_mailsent', 1); + } + } + } else { // !facebook_check_realtime_active() + del_config('facebook', 'realtime_err_mailsent'); + } + } + + set_config('facebook','last_poll', time()); } @@ -639,1118 +654,1118 @@ function facebook_cron($a,$b) { function facebook_plugin_settings(&$a,&$b) { - $b .= '
'; - $b .= '

' . t('Facebook') . '

'; - $b .= '' . t('Facebook Connector Settings') . '
'; - $b .= '
'; + $b .= '
'; + $b .= '

' . t('Facebook') . '

'; + $b .= '' . t('Facebook Connector Settings') . '
'; + $b .= '
'; } function facebook_plugin_admin(&$a, &$o){ - $o = ''; - - $o .= '

' . t('Facebook API Key') . '

'; - - $appid = get_config('facebook', 'appid' ); - $appsecret = get_config('facebook', 'appsecret' ); - $poll_interval = get_config('facebook', 'poll_interval' ); - if (!$poll_interval) $poll_interval = FACEBOOK_DEFAULT_POLL_INTERVAL; - - $ret1 = q("SELECT `v` FROM `config` WHERE `cat` = 'facebook' AND `k` = 'appid' LIMIT 1"); - $ret2 = q("SELECT `v` FROM `config` WHERE `cat` = 'facebook' AND `k` = 'appsecret' LIMIT 1"); - if ((count($ret1) > 0 && $ret1[0]['v'] != $appid) || (count($ret2) > 0 && $ret2[0]['v'] != $appsecret)) $o .= t('Error: it appears that you have specified the App-ID and -Secret in your .htconfig.php file. As long as they are specified there, they cannot be set using this form.

'); - - $working_connection = false; - if ($appid && $appsecret) { - $subs = facebook_subscriptions_get(); - if ($subs === null) $o .= t('Error: the given API Key seems to be incorrect (the application access token could not be retrieved).') . '
'; - elseif (is_array($subs)) { - $o .= t('The given API Key seems to work correctly.') . '
'; - $working_connection = true; - } else $o .= t('The correctness of the API Key could not be detected. Somthing strange\'s going on.') . '
'; - } - - $o .= '
'; - $o .= '
'; - $o .= '
'; - $o .= ''; - - if ($working_connection) { - $o .= '

' . t('Real-Time Updates') . '

'; - - $activated = facebook_check_realtime_active(); - if ($activated) { - $o .= t('Real-Time Updates are activated.') . '

'; - $o .= ''; - } else { - $o .= t('Real-Time Updates not activated.') . '
'; - } - } + $o = ''; + + $o .= '

' . t('Facebook API Key') . '

'; + + $appid = get_config('facebook', 'appid' ); + $appsecret = get_config('facebook', 'appsecret' ); + $poll_interval = get_config('facebook', 'poll_interval' ); + if (!$poll_interval) $poll_interval = FACEBOOK_DEFAULT_POLL_INTERVAL; + + $ret1 = q("SELECT `v` FROM `config` WHERE `cat` = 'facebook' AND `k` = 'appid' LIMIT 1"); + $ret2 = q("SELECT `v` FROM `config` WHERE `cat` = 'facebook' AND `k` = 'appsecret' LIMIT 1"); + if ((count($ret1) > 0 && $ret1[0]['v'] != $appid) || (count($ret2) > 0 && $ret2[0]['v'] != $appsecret)) $o .= t('Error: it appears that you have specified the App-ID and -Secret in your .htconfig.php file. As long as they are specified there, they cannot be set using this form.

'); + + $working_connection = false; + if ($appid && $appsecret) { + $subs = facebook_subscriptions_get(); + if ($subs === null) $o .= t('Error: the given API Key seems to be incorrect (the application access token could not be retrieved).') . '
'; + elseif (is_array($subs)) { + $o .= t('The given API Key seems to work correctly.') . '
'; + $working_connection = true; + } else $o .= t('The correctness of the API Key could not be detected. Somthing strange\'s going on.') . '
'; + } + + $o .= '
'; + $o .= '
'; + $o .= '
'; + $o .= ''; + + if ($working_connection) { + $o .= '

' . t('Real-Time Updates') . '

'; + + $activated = facebook_check_realtime_active(); + if ($activated) { + $o .= t('Real-Time Updates are activated.') . '

'; + $o .= ''; + } else { + $o .= t('Real-Time Updates not activated.') . '
'; + } + } } function facebook_plugin_admin_post(&$a, &$o){ - check_form_security_token_redirectOnErr('/admin/plugins/facebook', 'fbsave'); - - if (x($_REQUEST,'fb_save_keys')) { - set_config('facebook', 'appid', $_REQUEST['appid']); - set_config('facebook', 'appsecret', $_REQUEST['appsecret']); - $poll_interval = IntVal($_REQUEST['poll_interval']); - if ($poll_interval >= FACEBOOK_MIN_POLL_INTERVAL) set_config('facebook', 'poll_interval', $poll_interval); - del_config('facebook', 'app_access_token'); - info(t('The new values have been saved.')); - } - if (x($_REQUEST,'real_time_activate')) { - facebook_subscription_add_users(); - } - if (x($_REQUEST,'real_time_deactivate')) { - facebook_subscription_del_users(); - } + check_form_security_token_redirectOnErr('/admin/plugins/facebook', 'fbsave'); + + if (x($_REQUEST,'fb_save_keys')) { + set_config('facebook', 'appid', $_REQUEST['appid']); + set_config('facebook', 'appsecret', $_REQUEST['appsecret']); + $poll_interval = IntVal($_REQUEST['poll_interval']); + if ($poll_interval >= FACEBOOK_MIN_POLL_INTERVAL) set_config('facebook', 'poll_interval', $poll_interval); + del_config('facebook', 'app_access_token'); + info(t('The new values have been saved.')); + } + if (x($_REQUEST,'real_time_activate')) { + facebook_subscription_add_users(); + } + if (x($_REQUEST,'real_time_deactivate')) { + facebook_subscription_del_users(); + } } function facebook_jot_nets(&$a,&$b) { - if(! local_user()) - return; + if(! local_user()) + return; - $fb_post = get_pconfig(local_user(),'facebook','post'); - if(intval($fb_post) == 1) { - $fb_defpost = get_pconfig(local_user(),'facebook','post_by_default'); - $selected = ((intval($fb_defpost) == 1) ? ' checked="checked" ' : ''); - $b .= '
' - . t('Post to Facebook') . '
'; - } + $fb_post = get_pconfig(local_user(),'facebook','post'); + if(intval($fb_post) == 1) { + $fb_defpost = get_pconfig(local_user(),'facebook','post_by_default'); + $selected = ((intval($fb_defpost) == 1) ? ' checked="checked" ' : ''); + $b .= '
' + . t('Post to Facebook') . '
'; + } } function facebook_post_hook(&$a,&$b) { - if($b['deleted'] || ($b['created'] !== $b['edited'])) - return; + if($b['deleted'] || ($b['created'] !== $b['edited'])) + return; - /** - * Post to Facebook stream - */ + /** + * Post to Facebook stream + */ - require_once('include/group.php'); - require_once('include/html2plain.php'); + require_once('include/group.php'); + require_once('include/html2plain.php'); - logger('Facebook post'); + logger('Facebook post'); - $reply = false; - $likes = false; + $reply = false; + $likes = false; - $toplevel = (($b['id'] == $b['parent']) ? true : false); + $toplevel = (($b['id'] == $b['parent']) ? true : false); - $linking = ((get_pconfig($b['uid'],'facebook','no_linking')) ? 0 : 1); + $linking = ((get_pconfig($b['uid'],'facebook','no_linking')) ? 0 : 1); - if((! $toplevel) && ($linking)) { - $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", - intval($b['parent']), - intval($b['uid']) - ); - if(count($r) && substr($r[0]['uri'],0,4) === 'fb::') - $reply = substr($r[0]['uri'],4); - elseif(count($r) && substr($r[0]['extid'],0,4) === 'fb::') - $reply = substr($r[0]['extid'],4); - else - return; + if((! $toplevel) && ($linking)) { + $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", + intval($b['parent']), + intval($b['uid']) + ); + if(count($r) && substr($r[0]['uri'],0,4) === 'fb::') + $reply = substr($r[0]['uri'],4); + elseif(count($r) && substr($r[0]['extid'],0,4) === 'fb::') + $reply = substr($r[0]['extid'],4); + else + return; - $u = q("SELECT * FROM user where uid = %d limit 1", - intval($b['uid']) - ); - if(! count($u)) - return; + $u = q("SELECT * FROM user where uid = %d limit 1", + intval($b['uid']) + ); + if(! count($u)) + return; - // only accept comments from the item owner. Other contacts are unknown to FB. - - if(! link_compare($b['author-link'], $a->get_baseurl() . '/profile/' . $u[0]['nickname'])) - return; - + // only accept comments from the item owner. Other contacts are unknown to FB. - logger('facebook reply id=' . $reply); - } + if(! link_compare($b['author-link'], $a->get_baseurl() . '/profile/' . $u[0]['nickname'])) + return; - if(strstr($b['postopts'],'facebook') || ($b['private']) || ($reply)) { - if($b['private'] && $reply === false) { - $allow_people = expand_acl($b['allow_cid']); - $allow_groups = expand_groups(expand_acl($b['allow_gid'])); - $deny_people = expand_acl($b['deny_cid']); - $deny_groups = expand_groups(expand_acl($b['deny_gid'])); + logger('facebook reply id=' . $reply); + } - $recipients = array_unique(array_merge($allow_people,$allow_groups)); - $deny = array_unique(array_merge($deny_people,$deny_groups)); + if(strstr($b['postopts'],'facebook') || ($b['private']) || ($reply)) { - $allow_str = dbesc(implode(', ',$recipients)); - if($allow_str) { - $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $allow_str ) AND `network` = 'face'"); - $allow_arr = array(); - if(count($r)) - foreach($r as $rr) - $allow_arr[] = $rr['notify']; - } + if($b['private'] && $reply === false) { + $allow_people = expand_acl($b['allow_cid']); + $allow_groups = expand_groups(expand_acl($b['allow_gid'])); + $deny_people = expand_acl($b['deny_cid']); + $deny_groups = expand_groups(expand_acl($b['deny_gid'])); - $deny_str = dbesc(implode(', ',$deny)); - if($deny_str) { - $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $deny_str ) AND `network` = 'face'"); - $deny_arr = array(); - if(count($r)) - foreach($r as $rr) - $deny_arr[] = $rr['notify']; - } + $recipients = array_unique(array_merge($allow_people,$allow_groups)); + $deny = array_unique(array_merge($deny_people,$deny_groups)); - if(count($deny_arr) && (! count($allow_arr))) { + $allow_str = dbesc(implode(', ',$recipients)); + if($allow_str) { + $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $allow_str ) AND `network` = 'face'"); + $allow_arr = array(); + if(count($r)) + foreach($r as $rr) + $allow_arr[] = $rr['notify']; + } - // One or more FB folks were denied access but nobody on FB was specifically allowed access. - // This might cause the post to be open to public on Facebook, but only to selected members - // on another network. Since this could potentially leak a post to somebody who was denied, - // we will skip posting it to Facebook with a slightly vague but relevant message that will - // hopefully lead somebody to this code comment for a better explanation of what went wrong. + $deny_str = dbesc(implode(', ',$deny)); + if($deny_str) { + $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $deny_str ) AND `network` = 'face'"); + $deny_arr = array(); + if(count($r)) + foreach($r as $rr) + $deny_arr[] = $rr['notify']; + } - notice( t('Post to Facebook cancelled because of multi-network access permission conflict.') . EOL); - return; - } + if(count($deny_arr) && (! count($allow_arr))) { + // One or more FB folks were denied access but nobody on FB was specifically allowed access. + // This might cause the post to be open to public on Facebook, but only to selected members + // on another network. Since this could potentially leak a post to somebody who was denied, + // we will skip posting it to Facebook with a slightly vague but relevant message that will + // hopefully lead somebody to this code comment for a better explanation of what went wrong. - // if it's a private message but no Facebook members are allowed or denied, skip Facebook post + notice( t('Post to Facebook cancelled because of multi-network access permission conflict.') . EOL); + return; + } - if((! count($allow_arr)) && (! count($deny_arr))) - return; - } - if($b['verb'] == ACTIVITY_LIKE) - $likes = true; + // if it's a private message but no Facebook members are allowed or denied, skip Facebook post + if((! count($allow_arr)) && (! count($deny_arr))) + return; + } - $appid = get_config('facebook', 'appid' ); - $secret = get_config('facebook', 'appsecret' ); + if($b['verb'] == ACTIVITY_LIKE) + $likes = true; - if($appid && $secret) { - logger('facebook: have appid+secret'); + $appid = get_config('facebook', 'appid' ); + $secret = get_config('facebook', 'appsecret' ); - $fb_token = get_pconfig($b['uid'],'facebook','access_token'); + if($appid && $secret) { + logger('facebook: have appid+secret'); - // post to facebook if it's a public post and we've ticked the 'post to Facebook' box, - // or it's a private message with facebook participants - // or it's a reply or likes action to an existing facebook post + $fb_token = get_pconfig($b['uid'],'facebook','access_token'); - if($fb_token && ($toplevel || $b['private'] || $reply)) { - logger('facebook: able to post'); - require_once('library/facebook.php'); - require_once('include/bbcode.php'); - $msg = $b['body']; + // post to facebook if it's a public post and we've ticked the 'post to Facebook' box, + // or it's a private message with facebook participants + // or it's a reply or likes action to an existing facebook post - logger('Facebook post: original msg=' . $msg, LOGGER_DATA); + if($fb_token && ($toplevel || $b['private'] || $reply)) { + logger('facebook: able to post'); + require_once('library/facebook.php'); + require_once('include/bbcode.php'); - // make links readable before we strip the code + $msg = $b['body']; - // unless it's a dislike - just send the text as a comment + logger('Facebook post: original msg=' . $msg, LOGGER_DATA); - if($b['verb'] == ACTIVITY_DISLIKE) - $msg = trim(strip_tags(bbcode($msg))); + // make links readable before we strip the code - // Old code - /*$search_str = $a->get_baseurl() . '/search'; + // unless it's a dislike - just send the text as a comment - if(preg_match("/\[url=(.*?)\](.*?)\[\/url\]/is",$msg,$matches)) { + if($b['verb'] == ACTIVITY_DISLIKE) + $msg = trim(strip_tags(bbcode($msg))); - // don't use hashtags for message link + // Old code + /*$search_str = $a->get_baseurl() . '/search'; - if(strpos($matches[2],$search_str) === false) { - $link = $matches[1]; - if(substr($matches[2],0,5) != '[img]') - $linkname = $matches[2]; - } - } + if(preg_match("/\[url=(.*?)\](.*?)\[\/url\]/is",$msg,$matches)) { - // strip tag links to avoid link clutter, this really should be - // configurable because we're losing information + // don't use hashtags for message link - $msg = preg_replace("/\#\[url=(.*?)\](.*?)\[\/url\]/is",'#$2',$msg); + if(strpos($matches[2],$search_str) === false) { + $link = $matches[1]; + if(substr($matches[2],0,5) != '[img]') + $linkname = $matches[2]; + } + } - // provide the link separately for normal links - $msg = preg_replace("/\[url=(.*?)\](.*?)\[\/url\]/is",'$2 $1',$msg); + // strip tag links to avoid link clutter, this really should be + // configurable because we're losing information - if(preg_match("/\[img\](.*?)\[\/img\]/is",$msg,$matches)) - $image = $matches[1]; + $msg = preg_replace("/\#\[url=(.*?)\](.*?)\[\/url\]/is",'#$2',$msg); - $msg = preg_replace("/\[img\](.*?)\[\/img\]/is", t('Image: ') . '$1', $msg); + // provide the link separately for normal links + $msg = preg_replace("/\[url=(.*?)\](.*?)\[\/url\]/is",'$2 $1',$msg); - if((strpos($link,z_root()) !== false) && (! $image)) - $image = $a->get_baseurl() . '/images/friendica-64.jpg'; + if(preg_match("/\[img\](.*?)\[\/img\]/is",$msg,$matches)) + $image = $matches[1]; - $msg = trim(strip_tags(bbcode($msg)));*/ + $msg = preg_replace("/\[img\](.*?)\[\/img\]/is", t('Image: ') . '$1', $msg); - // New code + if((strpos($link,z_root()) !== false) && (! $image)) + $image = $a->get_baseurl() . '/images/friendica-64.jpg'; - // Looking for the first image - $image = ''; - if(preg_match("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/is",$b['body'],$matches)) - $image = $matches[3]; + $msg = trim(strip_tags(bbcode($msg)));*/ - if ($image != '') - if(preg_match("/\[img\](.*?)\[\/img\]/is",$b['body'],$matches)) - $image = $matches[1]; + // New code - // Checking for a bookmark element - $body = $b['body']; - if (strpos($body, "[bookmark") !== false) { - // splitting the text in two parts: - // before and after the bookmark - $pos = strpos($body, "[bookmark"); - $body1 = substr($body, 0, $pos); - $body2 = substr($body, $pos); + // Looking for the first image + $image = ''; + if(preg_match("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/is",$b['body'],$matches)) + $image = $matches[3]; - // Removing the bookmark and all quotes after the bookmark - // they are mostly only the content after the bookmark. - $body2 = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism",'',$body2); - $body2 = preg_replace("/\[quote\=([^\]]*)\](.*?)\[\/quote\]/ism",'',$body2); - $body2 = preg_replace("/\[quote\](.*?)\[\/quote\]/ism",'',$body2); + if ($image != '') + if(preg_match("/\[img\](.*?)\[\/img\]/is",$b['body'],$matches)) + $image = $matches[1]; - $body = $body1.$body2; - } + // Checking for a bookmark element + $body = $b['body']; + if (strpos($body, "[bookmark") !== false) { + // splitting the text in two parts: + // before and after the bookmark + $pos = strpos($body, "[bookmark"); + $body1 = substr($body, 0, $pos); + $body2 = substr($body, $pos); - // At first convert the text to html - $html = bbcode($body); - - // Then convert it to plain text - $msg = trim($b['title']." \n\n".html2plain($html, 0, true)); - $msg = html_entity_decode($msg,ENT_QUOTES,'UTF-8'); + // Removing the bookmark and all quotes after the bookmark + // they are mostly only the content after the bookmark. + $body2 = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism",'',$body2); + $body2 = preg_replace("/\[quote\=([^\]]*)\](.*?)\[\/quote\]/ism",'',$body2); + $body2 = preg_replace("/\[quote\](.*?)\[\/quote\]/ism",'',$body2); - // Removing multiple newlines - while (strpos($msg, "\n\n\n") !== false) - $msg = str_replace("\n\n\n", "\n\n", $msg); - - // add any attachments as text urls - $arr = explode(',',$b['attach']); - - if(count($arr)) { - $msg .= "\n"; - foreach($arr as $r) { - $matches = false; - $cnt = preg_match('|\[attach\]href=\"(.*?)\" size=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"\[\/attach\]|',$r,$matches); - if($cnt) { - $msg .= "\n".$matches[1]; - } - } - } - - $link = ''; - $linkname = ''; - // look for bookmark-bbcode and handle it with priority - if(preg_match("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/is",$b['body'],$matches)) { - $link = $matches[1]; - $linkname = $matches[2]; - } - - // If there is no bookmark element then take the first link - if ($link == '') { - $links = collecturls($html); - if (sizeof($links) > 0) { - reset($links); - $link = current($links); - } - } - - // Remove trailing and leading spaces - $msg = trim($msg); - - // Since facebook increased the maxpostlen massively this never should happen again :) - if (strlen($msg) > FACEBOOK_MAXPOSTLEN) { - $shortlink = ""; - require_once('library/slinky.php'); - - $display_url = $b['plink']; - - $slinky = new Slinky( $display_url ); - // setup a cascade of shortening services - // try to get a short link from these services - // in the order ur1.ca, trim, id.gd, tinyurl - $slinky->set_cascade( array( new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL() ) ); - $shortlink = $slinky->short(); - // the new message will be shortened such that "... $shortlink" - // will fit into the character limit - $msg = substr($msg, 0, FACEBOOK_MAXPOSTLEN - strlen($shortlink) - 4); - $msg .= '... ' . $shortlink; - } - - // Fallback - if message is empty - if(!strlen($msg)) - $msg = $link; - - if(!strlen($msg)) - $msg = $image; - - if(!strlen($msg)) - $msg = $linkname; - - // If there is nothing to post then exit - if(!strlen($msg)) - return; - - logger('Facebook post: msg=' . $msg, LOGGER_DATA); - - if($likes) { - $postvars = array('access_token' => $fb_token); - } - else { - $postvars = array( - 'access_token' => $fb_token, - 'message' => $msg - ); - if(isset($image)) - $postvars['picture'] = $image; - if(isset($link)) - $postvars['link'] = $link; - if(isset($linkname)) - $postvars['name'] = $linkname; - } - - if(($b['private']) && ($toplevel)) { - $postvars['privacy'] = '{"value": "CUSTOM", "friends": "SOME_FRIENDS"'; - if(count($allow_arr)) - $postvars['privacy'] .= ',"allow": "' . implode(',',$allow_arr) . '"'; - if(count($deny_arr)) - $postvars['privacy'] .= ',"deny": "' . implode(',',$deny_arr) . '"'; - $postvars['privacy'] .= '}'; - - } - - if($reply) { - $url = 'https://graph.facebook.com/' . $reply . '/' . (($likes) ? 'likes' : 'comments'); - } - else { - $url = 'https://graph.facebook.com/me/feed'; - if($b['plink']) - $postvars['actions'] = '{"name": "' . t('View on Friendica') . '", "link": "' . $b['plink'] . '"}'; - } - - logger('facebook: post to ' . $url); - logger('facebook: postvars: ' . print_r($postvars,true)); - - // "test_mode" prevents anything from actually being posted. - // Otherwise, let's do it. - - if(! get_config('facebook','test_mode')) { - $x = post_url($url, $postvars); - logger('Facebook post returns: ' . $x, LOGGER_DEBUG); - - $retj = json_decode($x); - if($retj->id) { - q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1", - dbesc('fb::' . $retj->id), - intval($b['id']) - ); - } - else { - if(! $likes) { - $s = serialize(array('url' => $url, 'item' => $b['id'], 'post' => $postvars)); - require_once('include/queue_fn.php'); - add_to_queue($a->contact,NETWORK_FACEBOOK,$s); - notice( t('Facebook post failed. Queued for retry.') . EOL); - } - - if (isset($retj->error) && $retj->error->type == "OAuthException" && $retj->error->code == 190) { - logger('Facebook session has expired due to changed password.', LOGGER_DEBUG); - - $last_notification = get_pconfig($b['uid'], 'facebook', 'session_expired_mailsent'); - if (!$last_notification || $last_notification < (time() - FACEBOOK_SESSION_ERR_NOTIFICATION_INTERVAL)) { - require_once('include/enotify.php'); - - $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($b['uid']) ); - notification(array( - 'uid' => $b['uid'], - 'type' => NOTIFY_SYSTEM, - 'system_type' => 'facebook_connection_invalid', - 'language' => $r[0]['language'], - 'to_name' => $r[0]['username'], - 'to_email' => $r[0]['email'], - 'source_name' => t('Administrator'), - 'source_link' => $a->config["system"]["url"], - 'source_photo' => $a->config["system"]["url"] . '/images/person-80.jpg', - )); - - set_pconfig($b['uid'], 'facebook', 'session_expired_mailsent', time()); - } else logger('Facebook: No notification, as the last one was sent on ' . $last_notification, LOGGER_DEBUG); - } - } - } - } - } - } + $body = $body1.$body2; + } + + // At first convert the text to html + $html = bbcode($body); + + // Then convert it to plain text + $msg = trim($b['title']." \n\n".html2plain($html, 0, true)); + $msg = html_entity_decode($msg,ENT_QUOTES,'UTF-8'); + + // Removing multiple newlines + while (strpos($msg, "\n\n\n") !== false) + $msg = str_replace("\n\n\n", "\n\n", $msg); + + // add any attachments as text urls + $arr = explode(',',$b['attach']); + + if(count($arr)) { + $msg .= "\n"; + foreach($arr as $r) { + $matches = false; + $cnt = preg_match('|\[attach\]href=\"(.*?)\" size=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"\[\/attach\]|',$r,$matches); + if($cnt) { + $msg .= "\n".$matches[1]; + } + } + } + + $link = ''; + $linkname = ''; + // look for bookmark-bbcode and handle it with priority + if(preg_match("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/is",$b['body'],$matches)) { + $link = $matches[1]; + $linkname = $matches[2]; + } + + // If there is no bookmark element then take the first link + if ($link == '') { + $links = collecturls($html); + if (sizeof($links) > 0) { + reset($links); + $link = current($links); + } + } + + // Remove trailing and leading spaces + $msg = trim($msg); + + // Since facebook increased the maxpostlen massively this never should happen again :) + if (strlen($msg) > FACEBOOK_MAXPOSTLEN) { + $shortlink = ""; + require_once('library/slinky.php'); + + $display_url = $b['plink']; + + $slinky = new Slinky( $display_url ); + // setup a cascade of shortening services + // try to get a short link from these services + // in the order ur1.ca, trim, id.gd, tinyurl + $slinky->set_cascade( array( new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL() ) ); + $shortlink = $slinky->short(); + // the new message will be shortened such that "... $shortlink" + // will fit into the character limit + $msg = substr($msg, 0, FACEBOOK_MAXPOSTLEN - strlen($shortlink) - 4); + $msg .= '... ' . $shortlink; + } + + // Fallback - if message is empty + if(!strlen($msg)) + $msg = $link; + + if(!strlen($msg)) + $msg = $image; + + if(!strlen($msg)) + $msg = $linkname; + + // If there is nothing to post then exit + if(!strlen($msg)) + return; + + logger('Facebook post: msg=' . $msg, LOGGER_DATA); + + if($likes) { + $postvars = array('access_token' => $fb_token); + } + else { + $postvars = array( + 'access_token' => $fb_token, + 'message' => $msg + ); + if(isset($image)) + $postvars['picture'] = $image; + if(isset($link)) + $postvars['link'] = $link; + if(isset($linkname)) + $postvars['name'] = $linkname; + } + + if(($b['private']) && ($toplevel)) { + $postvars['privacy'] = '{"value": "CUSTOM", "friends": "SOME_FRIENDS"'; + if(count($allow_arr)) + $postvars['privacy'] .= ',"allow": "' . implode(',',$allow_arr) . '"'; + if(count($deny_arr)) + $postvars['privacy'] .= ',"deny": "' . implode(',',$deny_arr) . '"'; + $postvars['privacy'] .= '}'; + + } + + if($reply) { + $url = 'https://graph.facebook.com/' . $reply . '/' . (($likes) ? 'likes' : 'comments'); + } + else { + $url = 'https://graph.facebook.com/me/feed'; + if($b['plink']) + $postvars['actions'] = '{"name": "' . t('View on Friendica') . '", "link": "' . $b['plink'] . '"}'; + } + + logger('facebook: post to ' . $url); + logger('facebook: postvars: ' . print_r($postvars,true)); + + // "test_mode" prevents anything from actually being posted. + // Otherwise, let's do it. + + if(! get_config('facebook','test_mode')) { + $x = post_url($url, $postvars); + logger('Facebook post returns: ' . $x, LOGGER_DEBUG); + + $retj = json_decode($x); + if($retj->id) { + q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1", + dbesc('fb::' . $retj->id), + intval($b['id']) + ); + } + else { + if(! $likes) { + $s = serialize(array('url' => $url, 'item' => $b['id'], 'post' => $postvars)); + require_once('include/queue_fn.php'); + add_to_queue($a->contact,NETWORK_FACEBOOK,$s); + notice( t('Facebook post failed. Queued for retry.') . EOL); + } + + if (isset($retj->error) && $retj->error->type == "OAuthException" && $retj->error->code == 190) { + logger('Facebook session has expired due to changed password.', LOGGER_DEBUG); + + $last_notification = get_pconfig($b['uid'], 'facebook', 'session_expired_mailsent'); + if (!$last_notification || $last_notification < (time() - FACEBOOK_SESSION_ERR_NOTIFICATION_INTERVAL)) { + require_once('include/enotify.php'); + + $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($b['uid']) ); + notification(array( + 'uid' => $b['uid'], + 'type' => NOTIFY_SYSTEM, + 'system_type' => 'facebook_connection_invalid', + 'language' => $r[0]['language'], + 'to_name' => $r[0]['username'], + 'to_email' => $r[0]['email'], + 'source_name' => t('Administrator'), + 'source_link' => $a->config["system"]["url"], + 'source_photo' => $a->config["system"]["url"] . '/images/person-80.jpg', + )); + + set_pconfig($b['uid'], 'facebook', 'session_expired_mailsent', time()); + } else logger('Facebook: No notification, as the last one was sent on ' . $last_notification, LOGGER_DEBUG); + } + } + } + } + } + } } function facebook_enotify(&$app, &$data) { - if (x($data, 'params') && $data['params']['type'] == NOTIFY_SYSTEM && x($data['params'], 'system_type') && $data['params']['system_type'] == 'facebook_connection_invalid') { - $data['itemlink'] = '/facebook'; - $data['epreamble'] = $data['preamble'] = t('Your Facebook connection became invalid. Please Re-authenticate.'); - $data['subject'] = t('Facebook connection became invalid'); - $data['body'] = sprintf( t("Hi %1\$s,\n\nThe connection between your accounts on %2\$s and Facebook became invalid. This usually happens after you change your Facebook-password. To enable the connection again, you have to %3\$sre-authenticate the Facebook-connector%4\$s."), $data['params']['to_name'], "[url=" . $app->config["system"]["url"] . "]" . $app->config["sitename"] . "[/url]", "[url=" . $app->config["system"]["url"] . "/facebook]", "[/url]"); - } + if (x($data, 'params') && $data['params']['type'] == NOTIFY_SYSTEM && x($data['params'], 'system_type') && $data['params']['system_type'] == 'facebook_connection_invalid') { + $data['itemlink'] = '/facebook'; + $data['epreamble'] = $data['preamble'] = t('Your Facebook connection became invalid. Please Re-authenticate.'); + $data['subject'] = t('Facebook connection became invalid'); + $data['body'] = sprintf( t("Hi %1\$s,\n\nThe connection between your accounts on %2\$s and Facebook became invalid. This usually happens after you change your Facebook-password. To enable the connection again, you have to %3\$sre-authenticate the Facebook-connector%4\$s."), $data['params']['to_name'], "[url=" . $app->config["system"]["url"] . "]" . $app->config["sitename"] . "[/url]", "[url=" . $app->config["system"]["url"] . "/facebook]", "[/url]"); + } } function facebook_post_local(&$a,&$b) { - // Figure out if Facebook posting is enabled for this post and file it in 'postopts' - // where we will discover it during background delivery. + // Figure out if Facebook posting is enabled for this post and file it in 'postopts' + // where we will discover it during background delivery. - // This can only be triggered by a local user posting to their own wall. + // This can only be triggered by a local user posting to their own wall. - if((local_user()) && (local_user() == $b['uid'])) { + if((local_user()) && (local_user() == $b['uid'])) { - $fb_post = intval(get_pconfig(local_user(),'facebook','post')); - $fb_enable = (($fb_post && x($_REQUEST,'facebook_enable')) ? intval($_REQUEST['facebook_enable']) : 0); + $fb_post = intval(get_pconfig(local_user(),'facebook','post')); + $fb_enable = (($fb_post && x($_REQUEST,'facebook_enable')) ? intval($_REQUEST['facebook_enable']) : 0); - // if API is used, default to the chosen settings - if($_REQUEST['api_source'] && intval(get_pconfig(local_user(),'facebook','post_by_default'))) - $fb_enable = 1; + // if API is used, default to the chosen settings + if($_REQUEST['api_source'] && intval(get_pconfig(local_user(),'facebook','post_by_default'))) + $fb_enable = 1; - if(! $fb_enable) - return; + if(! $fb_enable) + return; - if(strlen($b['postopts'])) - $b['postopts'] .= ','; - $b['postopts'] .= 'facebook'; - } + if(strlen($b['postopts'])) + $b['postopts'] .= ','; + $b['postopts'] .= 'facebook'; + } } function fb_queue_hook(&$a,&$b) { - $qi = q("SELECT * FROM `queue` WHERE `network` = '%s'", - dbesc(NETWORK_FACEBOOK) - ); - if(! count($qi)) - return; + $qi = q("SELECT * FROM `queue` WHERE `network` = '%s'", + dbesc(NETWORK_FACEBOOK) + ); + if(! count($qi)) + return; - require_once('include/queue_fn.php'); + require_once('include/queue_fn.php'); - foreach($qi as $x) { - if($x['network'] !== NETWORK_FACEBOOK) - continue; + foreach($qi as $x) { + if($x['network'] !== NETWORK_FACEBOOK) + continue; - logger('facebook_queue: run'); + logger('facebook_queue: run'); - $r = q("SELECT `user`.* FROM `user` LEFT JOIN `contact` on `contact`.`uid` = `user`.`uid` + $r = q("SELECT `user`.* FROM `user` LEFT JOIN `contact` on `contact`.`uid` = `user`.`uid` WHERE `contact`.`self` = 1 AND `contact`.`id` = %d LIMIT 1", - intval($x['cid']) - ); - if(! count($r)) - continue; + intval($x['cid']) + ); + if(! count($r)) + continue; - $user = $r[0]; + $user = $r[0]; - $appid = get_config('facebook', 'appid' ); - $secret = get_config('facebook', 'appsecret' ); + $appid = get_config('facebook', 'appid' ); + $secret = get_config('facebook', 'appsecret' ); - if($appid && $secret) { - $fb_post = intval(get_pconfig($user['uid'],'facebook','post')); - $fb_token = get_pconfig($user['uid'],'facebook','access_token'); + if($appid && $secret) { + $fb_post = intval(get_pconfig($user['uid'],'facebook','post')); + $fb_token = get_pconfig($user['uid'],'facebook','access_token'); - if($fb_post && $fb_token) { - logger('facebook_queue: able to post'); - require_once('library/facebook.php'); + if($fb_post && $fb_token) { + logger('facebook_queue: able to post'); + require_once('library/facebook.php'); - $z = unserialize($x['content']); - $item = $z['item']; - $j = post_url($z['url'],$z['post']); + $z = unserialize($x['content']); + $item = $z['item']; + $j = post_url($z['url'],$z['post']); - $retj = json_decode($j); - if($retj->id) { - q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1", - dbesc('fb::' . $retj->id), - intval($item) - ); - logger('facebook_queue: success: ' . $j); - remove_queue_item($x['id']); - } - else { - logger('facebook_queue: failed: ' . $j); - update_queue_time($x['id']); - } - } - } - } + $retj = json_decode($j); + if($retj->id) { + q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1", + dbesc('fb::' . $retj->id), + intval($item) + ); + logger('facebook_queue: success: ' . $j); + remove_queue_item($x['id']); + } + else { + logger('facebook_queue: failed: ' . $j); + update_queue_time($x['id']); + } + } + } + } } function fb_get_timeline($access_token, &$since) { + $entries = new stdClass(); + $entries->data = array(); + $newest = 0; - $entries->data = array(); - $newest = 0; + $url = 'https://graph.facebook.com/me/home?access_token='.$access_token; - $url = 'https://graph.facebook.com/me/home?access_token='.$access_token; + if ($since != 0) + $url .= "&since=".$since; - if ($since != 0) - $url .= "&since=".$since; + do { + $s = fetch_url($url); + $j = json_decode($s); + $oldestdate = time(); + if (isset($j->data)) + foreach ($j->data as $entry) { + $created = strtotime($entry->created_time); - do { - $s = fetch_url($url); - $j = json_decode($s); - $oldestdate = time(); - if (isset($j->data)) - foreach ($j->data as $entry) { - $created = strtotime($entry->created_time); + if ($newest < $created) + $newest = $created; - if ($newest < $created) - $newest = $created; + if ($created >= $since) + $entries->data[] = $entry; - if ($created >= $since) - $entries->data[] = $entry; + if ($created <= $oldestdate) + $oldestdate = $created; + } + else + break; - if ($created <= $oldestdate) - $oldestdate = $created; - } - else - break; + $url = $j->paging->next; - $url = $j->paging->next; + } while (($oldestdate > $since) and ($since != 0) and ($url != '')); - } while (($oldestdate > $since) and ($since != 0) and ($url != '')); + if ($newest > $since) + $since = $newest; - if ($newest > $since) - $since = $newest; - - return($entries); + return($entries); } function fb_consume_all($uid) { - require_once('include/items.php'); + require_once('include/items.php'); - $access_token = get_pconfig($uid,'facebook','access_token'); - if(! $access_token) - return; - - if(! get_pconfig($uid,'facebook','no_wall')) { - $private_wall = intval(get_pconfig($uid,'facebook','private_wall')); - $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token); - if($s) { - $j = json_decode($s); - if (isset($j->data)) { - logger('fb_consume_stream: wall: ' . print_r($j,true), LOGGER_DATA); - fb_consume_stream($uid,$j,($private_wall) ? false : true); - } else { - logger('fb_consume_stream: wall: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL); - } - } - } - // Get the last date - $lastdate = get_pconfig($uid,'facebook','lastdate'); - // fetch all items since the last date - $j = fb_get_timeline($access_token, &$lastdate); - if (isset($j->data)) { - logger('fb_consume_stream: feed: ' . print_r($j,true), LOGGER_DATA); - fb_consume_stream($uid,$j,false); + $access_token = get_pconfig($uid,'facebook','access_token'); + if(! $access_token) + return; - // Write back the last date - set_pconfig($uid,'facebook','lastdate', $lastdate); - } else - logger('fb_consume_stream: feed: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL); + if(! get_pconfig($uid,'facebook','no_wall')) { + $private_wall = intval(get_pconfig($uid,'facebook','private_wall')); + $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token); + if($s) { + $j = json_decode($s); + if (isset($j->data)) { + logger('fb_consume_stream: wall: ' . print_r($j,true), LOGGER_DATA); + fb_consume_stream($uid,$j,($private_wall) ? false : true); + } else { + logger('fb_consume_stream: wall: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL); + } + } + } + // Get the last date + $lastdate = get_pconfig($uid,'facebook','lastdate'); + // fetch all items since the last date + $j = fb_get_timeline($access_token, &$lastdate); + if (isset($j->data)) { + logger('fb_consume_stream: feed: ' . print_r($j,true), LOGGER_DATA); + fb_consume_stream($uid,$j,false); + + // Write back the last date + set_pconfig($uid,'facebook','lastdate', $lastdate); + } else + logger('fb_consume_stream: feed: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL); } function fb_get_photo($uid,$link) { - $access_token = get_pconfig($uid,'facebook','access_token'); - if(! $access_token || (! stristr($link,'facebook.com/photo.php'))) - return ""; - //return "\n" . '[url=' . $link . ']' . t('link') . '[/url]'; - $ret = preg_match('/fbid=([0-9]*)/',$link,$match); - if($ret) - $photo_id = $match[1]; - $x = fetch_url('https://graph.facebook.com/' . $photo_id . '?access_token=' . $access_token); - $j = json_decode($x); - if($j->picture) - return "\n\n" . '[url=' . $link . '][img]' . $j->picture . '[/img][/url]'; - //else - // return "\n" . '[url=' . $link . ']' . t('link') . '[/url]'; + $access_token = get_pconfig($uid,'facebook','access_token'); + if(! $access_token || (! stristr($link,'facebook.com/photo.php'))) + return ""; + //return "\n" . '[url=' . $link . ']' . t('link') . '[/url]'; + $ret = preg_match('/fbid=([0-9]*)/',$link,$match); + if($ret) + $photo_id = $match[1]; + $x = fetch_url('https://graph.facebook.com/' . $photo_id . '?access_token=' . $access_token); + $j = json_decode($x); + if($j->picture) + return "\n\n" . '[url=' . $link . '][img]' . $j->picture . '[/img][/url]'; + //else + // return "\n" . '[url=' . $link . ']' . t('link') . '[/url]'; } function fb_consume_stream($uid,$j,$wall = false) { - $a = get_app(); + $a = get_app(); - $user = q("SELECT * FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1", - intval($uid) - ); - if(! count($user)) - return; + $user = q("SELECT * FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1", + intval($uid) + ); + if(! count($user)) + return; - $my_local_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname']; + $my_local_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname']; - $no_linking = get_pconfig($uid,'facebook','no_linking'); - if($no_linking) - return; + $no_linking = get_pconfig($uid,'facebook','no_linking'); + if($no_linking) + return; - $self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1", - intval($uid) - ); + $self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1", + intval($uid) + ); - $blocked_apps = get_pconfig($uid,'facebook','blocked_apps'); - $blocked_apps_arr = explode(',',$blocked_apps); + $blocked_apps = get_pconfig($uid,'facebook','blocked_apps'); + $blocked_apps_arr = explode(',',$blocked_apps); - $self_id = get_pconfig($uid,'facebook','self_id'); - if(! count($j->data) || (! strlen($self_id))) - return; + $self_id = get_pconfig($uid,'facebook','self_id'); + if(! count($j->data) || (! strlen($self_id))) + return; - foreach($j->data as $entry) { - logger('fb_consume: entry: ' . print_r($entry,true), LOGGER_DATA); - $datarray = array(); + foreach($j->data as $entry) { + logger('fb_consume: entry: ' . print_r($entry,true), LOGGER_DATA); + $datarray = array(); - $r = q("SELECT * FROM `item` WHERE ( `uri` = '%s' OR `extid` = '%s') AND `uid` = %d LIMIT 1", - dbesc('fb::' . $entry->id), - dbesc('fb::' . $entry->id), - intval($uid) - ); - if(count($r)) { - $post_exists = true; - $orig_post = $r[0]; - $top_item = $r[0]['id']; - } - else { - $post_exists = false; - $orig_post = null; - } + $r = q("SELECT * FROM `item` WHERE ( `uri` = '%s' OR `extid` = '%s') AND `uid` = %d LIMIT 1", + dbesc('fb::' . $entry->id), + dbesc('fb::' . $entry->id), + intval($uid) + ); + if(count($r)) { + $post_exists = true; + $orig_post = $r[0]; + $top_item = $r[0]['id']; + } + else { + $post_exists = false; + $orig_post = null; + } - if(! $orig_post) { - $datarray['gravity'] = 0; - $datarray['uid'] = $uid; - $datarray['wall'] = (($wall) ? 1 : 0); - $datarray['uri'] = $datarray['parent-uri'] = 'fb::' . $entry->id; - $from = $entry->from; - if($from->id == $self_id) - $datarray['contact-id'] = $self[0]['id']; - else { - // Looking if user is known - if not he is added - $access_token = get_pconfig($uid, 'facebook', 'access_token'); - fb_get_friends_sync_new($uid, $access_token, $from); + if(! $orig_post) { + $datarray['gravity'] = 0; + $datarray['uid'] = $uid; + $datarray['wall'] = (($wall) ? 1 : 0); + $datarray['uri'] = $datarray['parent-uri'] = 'fb::' . $entry->id; + $from = $entry->from; + if($from->id == $self_id) + $datarray['contact-id'] = $self[0]['id']; + else { + // Looking if user is known - if not he is added + $access_token = get_pconfig($uid, 'facebook', 'access_token'); + fb_get_friends_sync_new($uid, $access_token, $from); - $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1", - dbesc($from->id), - intval($uid) - ); - if(count($r)) - $datarray['contact-id'] = $r[0]['id']; - } + $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1", + dbesc($from->id), + intval($uid) + ); + if(count($r)) + $datarray['contact-id'] = $r[0]['id']; + } - // don't store post if we don't have a contact - if(! x($datarray,'contact-id')) { - logger('facebook: no contact '.$from->name.' '.$from->id.'. post ignored'); - continue; - } + // don't store post if we don't have a contact + if(! x($datarray,'contact-id')) { + logger('facebook: no contact '.$from->name.' '.$from->id.'. post ignored'); + continue; + } - $datarray['verb'] = ACTIVITY_POST; - if($wall) { - $datarray['owner-name'] = $self[0]['name']; - $datarray['owner-link'] = $self[0]['url']; - $datarray['owner-avatar'] = $self[0]['thumb']; - } - if(isset($entry->application) && isset($entry->application->name) && strlen($entry->application->name)) - $datarray['app'] = strip_tags($entry->application->name); - else - $datarray['app'] = 'facebook'; + $datarray['verb'] = ACTIVITY_POST; + if($wall) { + $datarray['owner-name'] = $self[0]['name']; + $datarray['owner-link'] = $self[0]['url']; + $datarray['owner-avatar'] = $self[0]['thumb']; + } + if(isset($entry->application) && isset($entry->application->name) && strlen($entry->application->name)) + $datarray['app'] = strip_tags($entry->application->name); + else + $datarray['app'] = 'facebook'; - $found_blocked = false; + $found_blocked = false; - if(count($blocked_apps_arr)) { - foreach($blocked_apps_arr as $bad_appl) { - if(strlen(trim($bad_appl)) && (stristr($datarray['app'],trim($bad_appl)))) { - $found_blocked = true; - } - } - } - - if($found_blocked) { - logger('facebook: blocking application: ' . $datarray['app']); - continue; - } + if(count($blocked_apps_arr)) { + foreach($blocked_apps_arr as $bad_appl) { + if(strlen(trim($bad_appl)) && (stristr($datarray['app'],trim($bad_appl)))) { + $found_blocked = true; + } + } + } - $datarray['author-name'] = $from->name; - $datarray['author-link'] = 'http://facebook.com/profile.php?id=' . $from->id; - $datarray['author-avatar'] = 'https://graph.facebook.com/' . $from->id . '/picture'; - $datarray['plink'] = $datarray['author-link'] . '&v=wall&story_fbid=' . substr($entry->id,strpos($entry->id,'_') + 1); + if($found_blocked) { + logger('facebook: blocking application: ' . $datarray['app']); + continue; + } - logger('facebook: post '.$entry->id.' from '.$from->name); + $datarray['author-name'] = $from->name; + $datarray['author-link'] = 'http://facebook.com/profile.php?id=' . $from->id; + $datarray['author-avatar'] = 'https://graph.facebook.com/' . $from->id . '/picture'; + $datarray['plink'] = $datarray['author-link'] . '&v=wall&story_fbid=' . substr($entry->id,strpos($entry->id,'_') + 1); - $datarray['body'] = escape_tags($entry->message); + logger('facebook: post '.$entry->id.' from '.$from->name); - if($entry->name and $entry->link) - $datarray['body'] .= "\n\n[bookmark=".$entry->link."]".$entry->name."[/bookmark]"; - elseif ($entry->name) - $datarray['body'] .= "\n\n[b]" . $entry->name."[/b]"; + $datarray['body'] = escape_tags($entry->message); - if($entry->caption) { - if(!$entry->name and $entry->link) - $datarray['body'] .= "\n\n[bookmark=".$entry->link."]".$entry->caption."[/bookmark]"; - else - $datarray['body'] .= "[i]" . $entry->caption."[/i]\n"; - } + if($entry->name and $entry->link) + $datarray['body'] .= "\n\n[bookmark=".$entry->link."]".$entry->name."[/bookmark]"; + elseif ($entry->name) + $datarray['body'] .= "\n\n[b]" . $entry->name."[/b]"; - if(!$entry->caption and !$entry->name) { - if ($entry->link) - $datarray['body'] .= "\n[url]".$entry->link."[/url]\n"; - else - $datarray['body'] .= "\n"; - } + if($entry->caption) { + if(!$entry->name and $entry->link) + $datarray['body'] .= "\n\n[bookmark=".$entry->link."]".$entry->caption."[/bookmark]"; + else + $datarray['body'] .= "[i]" . $entry->caption."[/i]\n"; + } - $quote = ""; - if($entry->description) - $quote = $entry->description; + if(!$entry->caption and !$entry->name) { + if ($entry->link) + $datarray['body'] .= "\n[url]".$entry->link."[/url]\n"; + else + $datarray['body'] .= "\n"; + } - if ($entry->properties) - foreach ($entry->properties as $property) - $quote .= "\n".$property->name.": [url=".$property->href."]".$property->text."[/url]"; + $quote = ""; + if($entry->description) + $quote = $entry->description; - if ($quote) - $datarray['body'] .= "\n[quote]".$quote."[/quote]"; + if ($entry->properties) + foreach ($entry->properties as $property) + $quote .= "\n".$property->name.": [url=".$property->href."]".$property->text."[/url]"; - // Only import the picture when the message is no video - // oembed display a picture of the video as well - if ($entry->type != "video") { - if($entry->picture && $entry->link) { - $datarray['body'] .= "\n" . '[url=' . $entry->link . '][img]'.$entry->picture.'[/img][/url]'; - } - else { - if($entry->picture) - $datarray['body'] .= "\n" . '[img]' . $entry->picture . '[/img]'; - // if just a link, it may be a wall photo - check - if($entry->link) - $datarray['body'] .= fb_get_photo($uid,$entry->link); - } - } + if ($quote) + $datarray['body'] .= "\n[quote]".$quote."[/quote]"; - // Just as a test - to see if these are the missing entries - //if(trim($datarray['body']) == '') - // $datarray['body'] = $entry->story; + // Only import the picture when the message is no video + // oembed display a picture of the video as well + if ($entry->type != "video") { + if($entry->picture && $entry->link) { + $datarray['body'] .= "\n" . '[url=' . $entry->link . '][img]'.$entry->picture.'[/img][/url]'; + } + else { + if($entry->picture) + $datarray['body'] .= "\n" . '[img]' . $entry->picture . '[/img]'; + // if just a link, it may be a wall photo - check + if($entry->link) + $datarray['body'] .= fb_get_photo($uid,$entry->link); + } + } - if(trim($datarray['body']) == '') { - logger('facebook: empty body '.$entry->id.' '.print_r($entry, true)); - continue; - } + // Just as a test - to see if these are the missing entries + //if(trim($datarray['body']) == '') + // $datarray['body'] = $entry->story; - $datarray['body'] .= "\n"; + if(trim($datarray['body']) == '') { + logger('facebook: empty body '.$entry->id.' '.print_r($entry, true)); + continue; + } - if ($entry->icon) - $datarray['body'] .= "[img]".$entry->icon."[/img]   "; + $datarray['body'] .= "\n"; - if ($entry->actions) - foreach ($entry->actions as $action) - if (($action->name != "Comment") and ($action->name != "Like")) - $datarray['body'] .= "[url=".$action->link."]".$action->name."[/url]   "; + if ($entry->icon) + $datarray['body'] .= "[img]".$entry->icon."[/img]   "; - $datarray['body'] = trim($datarray['body']); + if ($entry->actions) + foreach ($entry->actions as $action) + if (($action->name != "Comment") and ($action->name != "Like")) + $datarray['body'] .= "[url=".$action->link."]".$action->name."[/url]   "; - //if(($datarray['body'] != '') and ($uid == 1)) - // $datarray['body'] .= "[noparse]".print_r($entry, true)."[/noparse]"; + $datarray['body'] = trim($datarray['body']); - if ($entry->place->name) - $datarray['coord'] = $entry->place->name; - else if ($entry->place->location->street or $entry->place->location->city or $entry->place->location->Denmark) { - if ($entry->place->location->street) - $datarray['coord'] = $entry->place->location->street; - if ($entry->place->location->city) - $datarray['coord'] .= " ".$entry->place->location->city; - if ($entry->place->location->country) - $datarray['coord'] .= " ".$entry->place->location->country; - } else if ($entry->place->location->latitude and $entry->place->location->longitude) - $datarray['coord'] = substr($entry->place->location->latitude, 0, 8) - .' '.substr($entry->place->location->longitude, 0, 8); + //if(($datarray['body'] != '') and ($uid == 1)) + // $datarray['body'] .= "[noparse]".print_r($entry, true)."[/noparse]"; - $datarray['created'] = datetime_convert('UTC','UTC',$entry->created_time); - $datarray['edited'] = datetime_convert('UTC','UTC',$entry->updated_time); + if ($entry->place->name) + $datarray['coord'] = $entry->place->name; + else if ($entry->place->location->street or $entry->place->location->city or $entry->place->location->Denmark) { + if ($entry->place->location->street) + $datarray['coord'] = $entry->place->location->street; + if ($entry->place->location->city) + $datarray['coord'] .= " ".$entry->place->location->city; + if ($entry->place->location->country) + $datarray['coord'] .= " ".$entry->place->location->country; + } else if ($entry->place->location->latitude and $entry->place->location->longitude) + $datarray['coord'] = substr($entry->place->location->latitude, 0, 8) + .' '.substr($entry->place->location->longitude, 0, 8); - // If the entry has a privacy policy, we cannot assume who can or cannot see it, - // as the identities are from a foreign system. Mark it as private to the owner. + $datarray['created'] = datetime_convert('UTC','UTC',$entry->created_time); + $datarray['edited'] = datetime_convert('UTC','UTC',$entry->updated_time); - if($entry->privacy && $entry->privacy->value !== 'EVERYONE') { - $datarray['private'] = 1; - $datarray['allow_cid'] = '<' . $self[0]['id'] . '>'; - } + // If the entry has a privacy policy, we cannot assume who can or cannot see it, + // as the identities are from a foreign system. Mark it as private to the owner. - $top_item = item_store($datarray); - $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", - intval($top_item), - intval($uid) - ); - if(count($r)) { - $orig_post = $r[0]; - logger('fb: new top level item posted'); - } - } + if($entry->privacy && $entry->privacy->value !== 'EVERYONE') { + $datarray['private'] = 1; + $datarray['allow_cid'] = '<' . $self[0]['id'] . '>'; + } - if(isset($entry->likes) && isset($entry->likes->data)) - $likers = $entry->likes->data; - else - $likers = null; + $top_item = item_store($datarray); + $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", + intval($top_item), + intval($uid) + ); + if(count($r)) { + $orig_post = $r[0]; + logger('fb: new top level item posted'); + } + } - if(isset($entry->comments) && isset($entry->comments->data)) - $comments = $entry->comments->data; - else - $comments = null; + if(isset($entry->likes) && isset($entry->likes->data)) + $likers = $entry->likes->data; + else + $likers = null; - if(is_array($likers)) { - foreach($likers as $likes) { + if(isset($entry->comments) && isset($entry->comments->data)) + $comments = $entry->comments->data; + else + $comments = null; - if(! $orig_post) - continue; + if(is_array($likers)) { + foreach($likers as $likes) { - // If we posted the like locally, it will be found with our url, not the FB url. + if(! $orig_post) + continue; - $second_url = (($likes->id == $self_id) ? $self[0]['url'] : 'http://facebook.com/profile.php?id=' . $likes->id); + // If we posted the like locally, it will be found with our url, not the FB url. - $r = q("SELECT * FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `verb` = '%s' + $second_url = (($likes->id == $self_id) ? $self[0]['url'] : 'http://facebook.com/profile.php?id=' . $likes->id); + + $r = q("SELECT * FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `verb` = '%s' AND ( `author-link` = '%s' OR `author-link` = '%s' ) LIMIT 1", - dbesc($orig_post['uri']), - intval($uid), - dbesc(ACTIVITY_LIKE), - dbesc('http://facebook.com/profile.php?id=' . $likes->id), - dbesc($second_url) - ); + dbesc($orig_post['uri']), + intval($uid), + dbesc(ACTIVITY_LIKE), + dbesc('http://facebook.com/profile.php?id=' . $likes->id), + dbesc($second_url) + ); - if(count($r)) - continue; - - $likedata = array(); - $likedata['parent'] = $top_item; - $likedata['verb'] = ACTIVITY_LIKE; - $likedata['gravity'] = 3; - $likedata['uid'] = $uid; - $likedata['wall'] = (($wall) ? 1 : 0); - $likedata['uri'] = item_new_uri($a->get_baseurl(), $uid); - $likedata['parent-uri'] = $orig_post['uri']; - if($likes->id == $self_id) - $likedata['contact-id'] = $self[0]['id']; - else { - $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1", - dbesc($likes->id), - intval($uid) - ); - if(count($r)) - $likedata['contact-id'] = $r[0]['id']; - } - if(! x($likedata,'contact-id')) - $likedata['contact-id'] = $orig_post['contact-id']; + if(count($r)) + continue; - $likedata['app'] = 'facebook'; - $likedata['verb'] = ACTIVITY_LIKE; - $likedata['author-name'] = $likes->name; - $likedata['author-link'] = 'http://facebook.com/profile.php?id=' . $likes->id; - $likedata['author-avatar'] = 'https://graph.facebook.com/' . $likes->id . '/picture'; - - $author = '[url=' . $likedata['author-link'] . ']' . $likedata['author-name'] . '[/url]'; - $objauthor = '[url=' . $orig_post['author-link'] . ']' . $orig_post['author-name'] . '[/url]'; - $post_type = t('status'); - $plink = '[url=' . $orig_post['plink'] . ']' . $post_type . '[/url]'; - $likedata['object-type'] = ACTIVITY_OBJ_NOTE; + $likedata = array(); + $likedata['parent'] = $top_item; + $likedata['verb'] = ACTIVITY_LIKE; + $likedata['gravity'] = 3; + $likedata['uid'] = $uid; + $likedata['wall'] = (($wall) ? 1 : 0); + $likedata['uri'] = item_new_uri($a->get_baseurl(), $uid); + $likedata['parent-uri'] = $orig_post['uri']; + if($likes->id == $self_id) + $likedata['contact-id'] = $self[0]['id']; + else { + $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1", + dbesc($likes->id), + intval($uid) + ); + if(count($r)) + $likedata['contact-id'] = $r[0]['id']; + } + if(! x($likedata,'contact-id')) + $likedata['contact-id'] = $orig_post['contact-id']; - $likedata['body'] = sprintf( t('%1$s likes %2$s\'s %3$s'), $author, $objauthor, $plink); - $likedata['object'] = '' . ACTIVITY_OBJ_NOTE . '1' . - '' . $orig_post['uri'] . '' . xmlify('') . '' . $orig_post['title'] . '' . $orig_post['body'] . ''; + $likedata['app'] = 'facebook'; + $likedata['verb'] = ACTIVITY_LIKE; + $likedata['author-name'] = $likes->name; + $likedata['author-link'] = 'http://facebook.com/profile.php?id=' . $likes->id; + $likedata['author-avatar'] = 'https://graph.facebook.com/' . $likes->id . '/picture'; - $item = item_store($likedata); - } - } - if(is_array($comments)) { - foreach($comments as $cmnt) { + $author = '[url=' . $likedata['author-link'] . ']' . $likedata['author-name'] . '[/url]'; + $objauthor = '[url=' . $orig_post['author-link'] . ']' . $orig_post['author-name'] . '[/url]'; + $post_type = t('status'); + $plink = '[url=' . $orig_post['plink'] . ']' . $post_type . '[/url]'; + $likedata['object-type'] = ACTIVITY_OBJ_NOTE; - if(! $orig_post) - continue; + $likedata['body'] = sprintf( t('%1$s likes %2$s\'s %3$s'), $author, $objauthor, $plink); + $likedata['object'] = '' . ACTIVITY_OBJ_NOTE . '1' . + '' . $orig_post['uri'] . '' . xmlify('') . '' . $orig_post['title'] . '' . $orig_post['body'] . ''; - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND ( `uri` = '%s' OR `extid` = '%s' ) LIMIT 1", - intval($uid), - dbesc('fb::' . $cmnt->id), - dbesc('fb::' . $cmnt->id) - ); - if(count($r)) - continue; + $item = item_store($likedata); + } + } + if(is_array($comments)) { + foreach($comments as $cmnt) { - $cmntdata = array(); - $cmntdata['parent'] = $top_item; - $cmntdata['verb'] = ACTIVITY_POST; - $cmntdata['gravity'] = 6; - $cmntdata['uid'] = $uid; - $cmntdata['wall'] = (($wall) ? 1 : 0); - $cmntdata['uri'] = 'fb::' . $cmnt->id; - $cmntdata['parent-uri'] = $orig_post['uri']; - if($cmnt->from->id == $self_id) { - $cmntdata['contact-id'] = $self[0]['id']; - } - else { - $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d LIMIT 1", - dbesc($cmnt->from->id), - intval($uid) - ); - if(count($r)) { - $cmntdata['contact-id'] = $r[0]['id']; - if($r[0]['blocked'] || $r[0]['readonly']) - continue; - } - } - if(! x($cmntdata,'contact-id')) - $cmntdata['contact-id'] = $orig_post['contact-id']; + if(! $orig_post) + continue; - $cmntdata['app'] = 'facebook'; - $cmntdata['created'] = datetime_convert('UTC','UTC',$cmnt->created_time); - $cmntdata['edited'] = datetime_convert('UTC','UTC',$cmnt->created_time); - $cmntdata['verb'] = ACTIVITY_POST; - $cmntdata['author-name'] = $cmnt->from->name; - $cmntdata['author-link'] = 'http://facebook.com/profile.php?id=' . $cmnt->from->id; - $cmntdata['author-avatar'] = 'https://graph.facebook.com/' . $cmnt->from->id . '/picture'; - $cmntdata['body'] = $cmnt->message; - $item = item_store($cmntdata); - - $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 ", - dbesc($orig_post['uri']), - intval($uid) - ); + $r = q("SELECT * FROM `item` WHERE `uid` = %d AND ( `uri` = '%s' OR `extid` = '%s' ) LIMIT 1", + intval($uid), + dbesc('fb::' . $cmnt->id), + dbesc('fb::' . $cmnt->id) + ); + if(count($r)) + continue; - if(count($myconv)) { - $importer_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname']; + $cmntdata = array(); + $cmntdata['parent'] = $top_item; + $cmntdata['verb'] = ACTIVITY_POST; + $cmntdata['gravity'] = 6; + $cmntdata['uid'] = $uid; + $cmntdata['wall'] = (($wall) ? 1 : 0); + $cmntdata['uri'] = 'fb::' . $cmnt->id; + $cmntdata['parent-uri'] = $orig_post['uri']; + if($cmnt->from->id == $self_id) { + $cmntdata['contact-id'] = $self[0]['id']; + } + else { + $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d LIMIT 1", + dbesc($cmnt->from->id), + intval($uid) + ); + if(count($r)) { + $cmntdata['contact-id'] = $r[0]['id']; + if($r[0]['blocked'] || $r[0]['readonly']) + continue; + } + } + if(! x($cmntdata,'contact-id')) + $cmntdata['contact-id'] = $orig_post['contact-id']; - foreach($myconv as $conv) { + $cmntdata['app'] = 'facebook'; + $cmntdata['created'] = datetime_convert('UTC','UTC',$cmnt->created_time); + $cmntdata['edited'] = datetime_convert('UTC','UTC',$cmnt->created_time); + $cmntdata['verb'] = ACTIVITY_POST; + $cmntdata['author-name'] = $cmnt->from->name; + $cmntdata['author-link'] = 'http://facebook.com/profile.php?id=' . $cmnt->from->id; + $cmntdata['author-avatar'] = 'https://graph.facebook.com/' . $cmnt->from->id . '/picture'; + $cmntdata['body'] = $cmnt->message; + $item = item_store($cmntdata); - // now if we find a match, it means we're in this conversation - - if(! link_compare($conv['author-link'],$importer_url)) - continue; + $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 ", + dbesc($orig_post['uri']), + intval($uid) + ); - require_once('include/enotify.php'); - - $conv_parent = $conv['parent']; + if(count($myconv)) { + $importer_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname']; - notification(array( - 'type' => NOTIFY_COMMENT, - 'notify_flags' => $user[0]['notify-flags'], - 'language' => $user[0]['language'], - 'to_name' => $user[0]['username'], - 'to_email' => $user[0]['email'], - 'uid' => $user[0]['uid'], - 'item' => $cmntdata, - 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $item, - 'source_name' => $cmntdata['author-name'], - 'source_link' => $cmntdata['author-link'], - 'source_photo' => $cmntdata['author-avatar'], - 'verb' => ACTIVITY_POST, - 'otype' => 'item', - 'parent' => $conv_parent, - )); + foreach($myconv as $conv) { - // only send one notification - break; - } - } - } - } - } + // now if we find a match, it means we're in this conversation + + if(! link_compare($conv['author-link'],$importer_url)) + continue; + + require_once('include/enotify.php'); + + $conv_parent = $conv['parent']; + + notification(array( + 'type' => NOTIFY_COMMENT, + 'notify_flags' => $user[0]['notify-flags'], + 'language' => $user[0]['language'], + 'to_name' => $user[0]['username'], + 'to_email' => $user[0]['email'], + 'uid' => $user[0]['uid'], + 'item' => $cmntdata, + 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $item, + 'source_name' => $cmntdata['author-name'], + 'source_link' => $cmntdata['author-link'], + 'source_photo' => $cmntdata['author-avatar'], + 'verb' => ACTIVITY_POST, + 'otype' => 'item', + 'parent' => $conv_parent, + )); + + // only send one notification + break; + } + } + } + } + } } function fb_get_app_access_token() { - - $acc_token = get_config('facebook','app_access_token'); - - if ($acc_token !== false) return $acc_token; - - $appid = get_config('facebook','appid'); - $appsecret = get_config('facebook', 'appsecret'); - - if ($appid === false || $appsecret === false) { - logger('fb_get_app_access_token: appid and/or appsecret not set', LOGGER_DEBUG); - return false; - } - logger('https://graph.facebook.com/oauth/access_token?client_id=' . $appid . '&client_secret=' . $appsecret . '&grant_type=client_credentials', LOGGER_DATA); - $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id=' . $appid . '&client_secret=' . $appsecret . '&grant_type=client_credentials'); - - if(strpos($x,'access_token=') !== false) { - logger('fb_get_app_access_token: returned access token: ' . $x, LOGGER_DATA); - - $token = str_replace('access_token=', '', $x); - if(strpos($token,'&') !== false) - $token = substr($token,0,strpos($token,'&')); - - if ($token == "") { - logger('fb_get_app_access_token: empty token: ' . $x, LOGGER_DEBUG); - return false; - } - set_config('facebook','app_access_token',$token); - return $token; - } else { - logger('fb_get_app_access_token: response did not contain an access_token: ' . $x, LOGGER_DATA); - return false; - } + + $acc_token = get_config('facebook','app_access_token'); + + if ($acc_token !== false) return $acc_token; + + $appid = get_config('facebook','appid'); + $appsecret = get_config('facebook', 'appsecret'); + + if ($appid === false || $appsecret === false) { + logger('fb_get_app_access_token: appid and/or appsecret not set', LOGGER_DEBUG); + return false; + } + logger('https://graph.facebook.com/oauth/access_token?client_id=' . $appid . '&client_secret=' . $appsecret . '&grant_type=client_credentials', LOGGER_DATA); + $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id=' . $appid . '&client_secret=' . $appsecret . '&grant_type=client_credentials'); + + if(strpos($x,'access_token=') !== false) { + logger('fb_get_app_access_token: returned access token: ' . $x, LOGGER_DATA); + + $token = str_replace('access_token=', '', $x); + if(strpos($token,'&') !== false) + $token = substr($token,0,strpos($token,'&')); + + if ($token == "") { + logger('fb_get_app_access_token: empty token: ' . $x, LOGGER_DEBUG); + return false; + } + set_config('facebook','app_access_token',$token); + return $token; + } else { + logger('fb_get_app_access_token: response did not contain an access_token: ' . $x, LOGGER_DATA); + return false; + } } function facebook_subscription_del_users() { - $a = get_app(); - $access_token = fb_get_app_access_token(); - - $url = "https://graph.facebook.com/" . get_config('facebook', 'appid' ) . "/subscriptions?access_token=" . $access_token; - facebook_delete_url($url); - - if (!facebook_check_realtime_active()) del_config('facebook', 'realtime_active'); + $a = get_app(); + $access_token = fb_get_app_access_token(); + + $url = "https://graph.facebook.com/" . get_config('facebook', 'appid' ) . "/subscriptions?access_token=" . $access_token; + facebook_delete_url($url); + + if (!facebook_check_realtime_active()) del_config('facebook', 'realtime_active'); } function facebook_subscription_add_users($second_try = false) { - $a = get_app(); - $access_token = fb_get_app_access_token(); - - $url = "https://graph.facebook.com/" . get_config('facebook', 'appid' ) . "/subscriptions?access_token=" . $access_token; - - list($usec, $sec) = explode(" ", microtime()); - $verify_token = sha1($usec . $sec . rand(0, 999999999)); - set_config('facebook', 'cb_verify_token', $verify_token); - - $cb = $a->get_baseurl() . '/facebook/?realtime_cb=1'; - - $j = post_url($url,array( - "object" => "user", - "fields" => "feed,friends", - "callback_url" => $cb, - "verify_token" => $verify_token, - )); - del_config('facebook', 'cb_verify_token'); - - if ($j) { - $x = json_decode($j); - logger("Facebook reponse: " . $j, LOGGER_DATA); - if (isset($x->error)) { - logger('facebook_subscription_add_users: got an error: ' . $j); - if ($x->error->type == "OAuthException" && $x->error->code == 190) { - del_config('facebook', 'app_access_token'); - if ($second_try === false) facebook_subscription_add_users(true); - } - } else { - logger('facebook_subscription_add_users: sucessful'); - if (facebook_check_realtime_active()) set_config('facebook', 'realtime_active', 1); - } - }; + $a = get_app(); + $access_token = fb_get_app_access_token(); + + $url = "https://graph.facebook.com/" . get_config('facebook', 'appid' ) . "/subscriptions?access_token=" . $access_token; + + list($usec, $sec) = explode(" ", microtime()); + $verify_token = sha1($usec . $sec . rand(0, 999999999)); + set_config('facebook', 'cb_verify_token', $verify_token); + + $cb = $a->get_baseurl() . '/facebook/?realtime_cb=1'; + + $j = post_url($url,array( + "object" => "user", + "fields" => "feed,friends", + "callback_url" => $cb, + "verify_token" => $verify_token, + )); + del_config('facebook', 'cb_verify_token'); + + if ($j) { + $x = json_decode($j); + logger("Facebook reponse: " . $j, LOGGER_DATA); + if (isset($x->error)) { + logger('facebook_subscription_add_users: got an error: ' . $j); + if ($x->error->type == "OAuthException" && $x->error->code == 190) { + del_config('facebook', 'app_access_token'); + if ($second_try === false) facebook_subscription_add_users(true); + } + } else { + logger('facebook_subscription_add_users: sucessful'); + if (facebook_check_realtime_active()) set_config('facebook', 'realtime_active', 1); + } + }; } function facebook_subscriptions_get() { - - $access_token = fb_get_app_access_token(); - if (!$access_token) return null; - - $url = "https://graph.facebook.com/" . get_config('facebook', 'appid' ) . "/subscriptions?access_token=" . $access_token; - $j = fetch_url($url); - $ret = null; - if ($j) { - $x = json_decode($j); - if (isset($x->data)) $ret = $x->data; - } - return $ret; + + $access_token = fb_get_app_access_token(); + if (!$access_token) return null; + + $url = "https://graph.facebook.com/" . get_config('facebook', 'appid' ) . "/subscriptions?access_token=" . $access_token; + $j = fetch_url($url); + $ret = null; + if ($j) { + $x = json_decode($j); + if (isset($x->data)) $ret = $x->data; + } + return $ret; } function facebook_check_realtime_active() { - $ret = facebook_subscriptions_get(); - if (is_null($ret)) return false; - if (is_array($ret)) foreach ($ret as $re) if (is_object($re) && $re->object == "user") return true; - return false; + $ret = facebook_subscriptions_get(); + if (is_null($ret)) return false; + if (is_array($ret)) foreach ($ret as $re) if (is_object($re) && $re->object == "user") return true; + return false; } @@ -1759,85 +1774,85 @@ function facebook_check_realtime_active() { // DELETE-request to $url if(! function_exists('facebook_delete_url')) { -function facebook_delete_url($url,$headers = null, &$redirects = 0, $timeout = 0) { - $a = get_app(); - $ch = curl_init($url); - if(($redirects > 8) || (! $ch)) - return false; + function facebook_delete_url($url,$headers = null, &$redirects = 0, $timeout = 0) { + $a = get_app(); + $ch = curl_init($url); + if(($redirects > 8) || (! $ch)) + return false; - curl_setopt($ch, CURLOPT_HEADER, true); - curl_setopt($ch, CURLOPT_RETURNTRANSFER,true); - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE"); - curl_setopt($ch, CURLOPT_USERAGENT, "Friendica"); + curl_setopt($ch, CURLOPT_HEADER, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER,true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE"); + curl_setopt($ch, CURLOPT_USERAGENT, "Friendica"); - if(intval($timeout)) { - curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); - } - else { - $curl_time = intval(get_config('system','curl_timeout')); - curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60)); - } - - if(defined('LIGHTTPD')) { - if(!is_array($headers)) { - $headers = array('Expect:'); - } else { - if(!in_array('Expect:', $headers)) { - array_push($headers, 'Expect:'); - } - } - } - if($headers) - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - - $check_cert = get_config('system','verifyssl'); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false)); - $prx = get_config('system','proxy'); - if(strlen($prx)) { - curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1); - curl_setopt($ch, CURLOPT_PROXY, $prx); - $prxusr = get_config('system','proxyuser'); - if(strlen($prxusr)) - curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr); - } - - $a->set_curl_code(0); - - // don't let curl abort the entire application - // if it throws any errors. - - $s = @curl_exec($ch); - - $base = $s; - $curl_info = curl_getinfo($ch); - $http_code = $curl_info['http_code']; - - $header = ''; - - // Pull out multiple headers, e.g. proxy and continuation headers - // allow for HTTP/2.x without fixing code - - while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) { - $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4); - $header .= $chunk; - $base = substr($base,strlen($chunk)); - } - - if($http_code == 301 || $http_code == 302 || $http_code == 303) { - $matches = array(); - preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches); - $url = trim(array_pop($matches)); - $url_parsed = @parse_url($url); - if (isset($url_parsed)) { - $redirects++; - return delete_url($url,$headers,$redirects,$timeout); + if(intval($timeout)) { + curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); + } + else { + $curl_time = intval(get_config('system','curl_timeout')); + curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60)); } - } - $a->set_curl_code($http_code); - $body = substr($s,strlen($header)); - $a->set_curl_headers($header); + if(defined('LIGHTTPD')) { + if(!is_array($headers)) { + $headers = array('Expect:'); + } else { + if(!in_array('Expect:', $headers)) { + array_push($headers, 'Expect:'); + } + } + } + if($headers) + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_close($ch); - return($body); -}} + $check_cert = get_config('system','verifyssl'); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false)); + $prx = get_config('system','proxy'); + if(strlen($prx)) { + curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1); + curl_setopt($ch, CURLOPT_PROXY, $prx); + $prxusr = get_config('system','proxyuser'); + if(strlen($prxusr)) + curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr); + } + + $a->set_curl_code(0); + + // don't let curl abort the entire application + // if it throws any errors. + + $s = @curl_exec($ch); + + $base = $s; + $curl_info = curl_getinfo($ch); + $http_code = $curl_info['http_code']; + + $header = ''; + + // Pull out multiple headers, e.g. proxy and continuation headers + // allow for HTTP/2.x without fixing code + + while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) { + $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4); + $header .= $chunk; + $base = substr($base,strlen($chunk)); + } + + if($http_code == 301 || $http_code == 302 || $http_code == 303) { + $matches = array(); + preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches); + $url = trim(array_pop($matches)); + $url_parsed = @parse_url($url); + if (isset($url_parsed)) { + $redirects++; + return delete_url($url,$headers,$redirects,$timeout); + } + } + $a->set_curl_code($http_code); + $body = substr($s,strlen($header)); + + $a->set_curl_headers($header); + + curl_close($ch); + return($body); + }} From 64cbef4f0de5a40a493060261ddb96fd1e467361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B6=C3=9Fl?= Date: Mon, 9 Apr 2012 12:07:24 +0000 Subject: [PATCH 02/96] Provide a link to Martin Farrent's instructions instead of the obsolete README text; Prevent some Notices when running on E_ALL --- facebook/README | 40 ++---------------------------- facebook/facebook.php | 57 ++++++++++++------------------------------- 2 files changed, 18 insertions(+), 79 deletions(-) diff --git a/facebook/README b/facebook/README index b68ba3da..5f74d2d4 100755 --- a/facebook/README +++ b/facebook/README @@ -1,43 +1,7 @@ Installing the Friendica/Facebook connector -1. Visit https://developers.facebook.com/apps to register an app. - a) Click "Create a new app" - b) We'd be very happy if you include "Friendica" in the application name - to increase name recognition. - c) Edit your app settings on the setup page. The Friendica icons are present - in the images directory and may be uploaded as a Facebook app icon. Use - images/friendica-16.jpg for the Icon and images/Friendica-128.jpg for the logo. - d) In the App Display name enter the name of your app (this should default to the - name you chose in part a). - e) Enter YourDomain.com in the App Domain field and hit return. - f) In "Select how your app connects with Facebook select "Website" and enter the - full URL to your Friendica install including HTTPS and a trailing slash. - -2. Enable the Facebook plugin by clicking on the icon next to it's name on the plugin - page of your admin panel. - b) return to the Facebook plugin page in your admin panel, and fill in the App-ID - and Application Secret settings you got from Facebook. - c) Click save. - d) Finally, return to the Facebook settings page, and activate real-time updates. - - i. If you for any reason prefer to use a configuration file instead of the admin panels, - Activate the plugin by including it in .htconfig.php, e.g. - - $a->config['system']['addon'] = 'plugin1,plugin2,facebook'; - - and set the following values: - $a->config['facebook']['appid'] = 'xxxxxxxxxxx'; - $a->config['facebook']['appsecret'] = 'xxxxxxxxxxxxxxx'; - - Replace with the settings Facebook gives you. - - -3. To use the Facebook plugin, visit the "connector settings" area of your settings - page. Click "Install Facebook Connector". -4. This will ask you to login to Facebook and allow the plugin to do it's stuff. - Allow it to do so. -5. You're done. To turn it off visit the Plugin Settings page again and - 'Remove Facebook posting'. +Detailed instructions how to use this plugin can be found at +https://github.com/friendica/friendica/wiki/How-to:-Friendica%E2%80%99s-Facebook-connector Vidoes and embeds will not be posted if there is no other content. Links and images will be converted to a format suitable for the Facebook API and diff --git a/facebook/facebook.php b/facebook/facebook.php index 0c31da1f..4e886608 100644 --- a/facebook/facebook.php +++ b/facebook/facebook.php @@ -1,7 +1,7 @@ * Tobias Hößl */ @@ -9,33 +9,8 @@ /** * Installing the Friendica/Facebook connector * - * 1. register an API key for your site from developer.facebook.com - * a. We'd be very happy if you include "Friendica" in the application name - * to increase name recognition. The Friendica icons are also present - * in the images directory and may be uploaded as a Facebook app icon. - * Use images/friendica-16.jpg for the Icon and images/friendica-128.jpg for the Logo. - * b. The url should be your site URL with a trailing slash. - * Friendica is a software application and does not require a Privacy Policy - * or Terms of Service, though your installation of it might. Facebook may require - * that you provide a Privacy Policy, which we find ironic. - * c. Set the following values in your .htconfig.php file - * $a->config['facebook']['appid'] = 'xxxxxxxxxxx'; - * $a->config['facebook']['appsecret'] = 'xxxxxxxxxxxxxxx'; - * Replace with the settings Facebook gives you. - * d. Navigate to Set Web->Site URL & Domain -> Website Settings. Set - * Site URL to yoursubdomain.yourdomain.com. Set Site Domain to your - * yourdomain.com. - * 2. Visit the Facebook Settings section of the "Settings->Plugin Settings" page. - * and click 'Install Facebook Connector'. - * 3. Visit the Facebook Settings section of the "Settings->Plugin Settings" page. - * and click 'Install Facebook Connector'. - * 4. This will ask you to login to Facebook and grant permission to the - * plugin to do its stuff. Allow it to do so. - * 5. Optional step: If you want to use Facebook Real Time Updates (so new messages - * and new contacts are added ~1min after they are postet / added on FB), go to - * Settings -> plugins -> facebook and press the "Activate Real-Time Updates"-button. - * 6. You're done. To turn it off visit the Plugin Settings page again and - * 'Remove Facebook posting'. + * Detailed instructions how to use this plugin can be found at + * https://github.com/friendica/friendica/wiki/How-to:-Friendica%E2%80%99s-Facebook-connector * * Vidoes and embeds will not be posted if there is no other content. Links * and images will be converted to a format suitable for the Facebook API and @@ -1391,32 +1366,32 @@ function fb_consume_stream($uid,$j,$wall = false) { logger('facebook: post '.$entry->id.' from '.$from->name); - $datarray['body'] = escape_tags($entry->message); + $datarray['body'] = (x($entry, 'message') ? escape_tags($entry->message) : ''); - if($entry->name and $entry->link) + if(x($entry, 'name') and x($entry, 'link')) $datarray['body'] .= "\n\n[bookmark=".$entry->link."]".$entry->name."[/bookmark]"; - elseif ($entry->name) + elseif (x($entry, 'name')) $datarray['body'] .= "\n\n[b]" . $entry->name."[/b]"; - if($entry->caption) { - if(!$entry->name and $entry->link) + if(x($entry, 'caption')) { + if(!x($entry, 'name') and x($entry, 'link')) $datarray['body'] .= "\n\n[bookmark=".$entry->link."]".$entry->caption."[/bookmark]"; else $datarray['body'] .= "[i]" . $entry->caption."[/i]\n"; } - if(!$entry->caption and !$entry->name) { - if ($entry->link) + if(!x($entry, 'caption') and !x($entry, 'name')) { + if (x($entry, 'link')) $datarray['body'] .= "\n[url]".$entry->link."[/url]\n"; else $datarray['body'] .= "\n"; } - $quote = ""; - if($entry->description) + $quote = ''; + if(x($entry, 'description')) $quote = $entry->description; - if ($entry->properties) + if (x($entry, 'properties')) foreach ($entry->properties as $property) $quote .= "\n".$property->name.": [url=".$property->href."]".$property->text."[/url]"; @@ -1426,14 +1401,14 @@ function fb_consume_stream($uid,$j,$wall = false) { // Only import the picture when the message is no video // oembed display a picture of the video as well if ($entry->type != "video") { - if($entry->picture && $entry->link) { + if(x($entry, 'picture') && x($entry, 'link')) { $datarray['body'] .= "\n" . '[url=' . $entry->link . '][img]'.$entry->picture.'[/img][/url]'; } else { - if($entry->picture) + if(x($entry, 'picture')) $datarray['body'] .= "\n" . '[img]' . $entry->picture . '[/img]'; // if just a link, it may be a wall photo - check - if($entry->link) + if(x($entry, 'link')) $datarray['body'] .= fb_get_photo($uid,$entry->link); } } From c9ccd91dd96baa61e4c075ab4e099ce436a584ec Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Mon, 9 Apr 2012 21:28:11 +0200 Subject: [PATCH 03/96] New addon "convpath": It converts all local links according to the current scheme (http/https) --- convpath/README | 7 ++++++ convpath/convpath.php | 50 +++++++++++++++++++++++++++++++++++++++++++ facebook/facebook.php | 10 +++++---- 3 files changed, 63 insertions(+), 4 deletions(-) create mode 100755 convpath/README create mode 100644 convpath/convpath.php diff --git a/convpath/README b/convpath/README new file mode 100755 index 00000000..9d1c3072 --- /dev/null +++ b/convpath/README @@ -0,0 +1,7 @@ +convpath + +This addon converts all internal paths according to the current scheme. + +That means that if a page is called via https then all internal links are also converted into https. + +Same happens when you call your page with http. diff --git a/convpath/convpath.php b/convpath/convpath.php new file mode 100644 index 00000000..5ad7fbcb --- /dev/null +++ b/convpath/convpath.php @@ -0,0 +1,50 @@ + + * + */ + +function convpath_install() { + register_hook('page_end', 'addon/convpath/convpath.php', 'convpath_page_end'); + register_hook('page_header', 'addon/convpath/convpath.php', 'convpath_page_header'); +} + + +function convpath_uninstall() { + unregister_hook('page_end', 'addon/convpath/convpath.php', 'convpath_page_end'); + unregister_hook('page_header', 'addon/convpath/convpath.php', 'convpath_page_header'); +} + +function convpath_page_header(&$a, &$o){ + $o = convpath_convert($o); +} + +function convpath_page_end(&$a, &$o){ + $o = convpath_convert($o); + $a->page['aside'] = convpath_convert($a->page['aside']); +} + +/* +Converts a given path according to the current scheme +*/ +function convpath_convert($path) { + global $a; + + if ($path == "") + return(""); + + $ssl = (substr($a->get_baseurl(), 0, 8) == "https://"); + + if ($ssl) { + $search = "http://".$a->get_hostname(); + $replace = "https://".$a->get_hostname(); + } else { + $search = "https://".$a->get_hostname(); + $replace = "http://".$a->get_hostname(); + } + $path = str_replace($search, $replace, $path); + return($path); +} diff --git a/facebook/facebook.php b/facebook/facebook.php index 70a353dd..5990a99e 100755 --- a/facebook/facebook.php +++ b/facebook/facebook.php @@ -1447,11 +1447,13 @@ function fb_consume_stream($uid,$j,$wall = false) { //if(($datarray['body'] != '') and ($uid == 1)) // $datarray['body'] .= "[noparse]".print_r($entry, true)."[/noparse]"; - if ($entry->place->name) - $datarray['coord'] = $entry->place->name; - else if ($entry->place->location->street or $entry->place->location->city or $entry->place->location->Denmark) { + if ($entry->place->name or $entry->place->location->street or + $entry->place->location->city or $entry->place->location->Denmark) { + $datarray['coord'] = ''; + if ($entry->place->name) + $datarray['coord'] .= $entry->place->name; if ($entry->place->location->street) - $datarray['coord'] = $entry->place->location->street; + $datarray['coord'] .= $entry->place->location->street; if ($entry->place->location->city) $datarray['coord'] .= " ".$entry->place->location->city; if ($entry->place->location->country) From acebf88aa5ceb6a613232c4bedbb3822c592f92d Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Mon, 9 Apr 2012 22:05:22 +0200 Subject: [PATCH 04/96] =?UTF-8?q?New=20addon=20"fromgplus".=20Should=20imp?= =?UTF-8?q?ort=20=C3posts=20from=20Google+.=20Not=20working=20by=20now.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fromgplus/fromgplus.php | 181 ++++++++++++++++++++++++++++++++++++++ fromgplus/tofriendica.php | 128 +++++++++++++++++++++++++++ 2 files changed, 309 insertions(+) create mode 100755 fromgplus/fromgplus.php create mode 100644 fromgplus/tofriendica.php diff --git a/fromgplus/fromgplus.php b/fromgplus/fromgplus.php new file mode 100755 index 00000000..09b6c605 --- /dev/null +++ b/fromgplus/fromgplus.php @@ -0,0 +1,181 @@ + + * + */ + +function fromgplus_install() { + register_hook('plugin_settings', 'addon/fromgplus/fromgplus.php', 'fromgplus_addon_settings'); + register_hook('plugin_settings_post', 'addon/fromgplus/fromgplus.php', 'fromgplus_addon_settings_post'); +} + +function fromgplus_uninstall() { + unregister_hook('plugin_settings', 'addon/fromgplus/fromgplus.php', 'fromgplus_addon_settings'); + unregister_hook('plugin_settings_post', 'addon/fromgplus/fromgplus.php', 'fromgplus_addon_settings_post'); +} + +function fromgplus_addon_settings(&$a,&$s) { + + if(! local_user()) + return; + + $enable_checked = (intval(get_pconfig(local_user(),'fromgplus','enable')) ? ' checked="checked"' : ''); + $account = get_pconfig(local_user(),'fromgplus','account'); + + $s .= '
'; + $s .= '

' . t('Google+ Import Settings').'

'; + $s .= '
'; + + $s .= ''; + $s .= ''; + $s .= '
'; + $s .= ''; + $s .= ''; + $s .= '
'; + + $s .= '
'; + $s .= '
'; + + return; +} + +function fromgplus_addon_settings_post(&$a,&$b) { + + if(! local_user()) + return; + + if($_POST['fromgplus-submit']) { + set_pconfig(local_user(),'fromgplus','account',trim($_POST['fromgplus-account'])); + $enable = ((x($_POST,'fromgplus-enable')) ? intval($_POST['fromgplus-enable']) : 0); + set_pconfig(local_user(),'fromgplus','enable', $enable); + info( t('Google+ Import Settings saved.') . EOL); + } +} +/* +function html2bbcode($html) { + + $bbcode = html_entity_decode($html, ENT_QUOTES, 'UTF-8'); + + $bbcode = str_replace(array("\n"), array(""), $bbcode); + $bbcode = str_replace(array("", ""), array("[b]", "[/b]"), $bbcode); + $bbcode = str_replace(array("", ""), array("[i]", "[/i]"), $bbcode); + $bbcode = str_replace(array("", ""), array("[s]", "[/s]"), $bbcode); + $bbcode = str_replace(array("
"), array("\n"), $bbcode); + + $bbcode = trim(strip_tags($bbcode)); + return($bbcode); +} + +function friendicapost($post) { + global $friendica; + + $api = new Statusnet($friendica["user"], $friendica["pw"], "GooglePlus", $friendica["server"]); + $ret = $api->updateStatus($post); + $api->endSession(); +} + +function handleattachments($item) { + $post = ""; + + foreach ($item->object->attachments as $attachment) { + switch($attachment->objectType) { + case "video": + //$post .= "\n\n[url=".$attachment->url."]". + // "[size=large][b]".html2bbcode($attachment->displayName)."[/b][/size][/url]\n"; + $post .= "\n\n[bookmark=".$attachment->url."]".html2bbcode($attachment->displayName)."[/bookmark]\n"; + + //if (strpos($attachment->embed->url, "youtube.com")) + // $post .= "[youtube]".$attachment->url."[/youtube]\n"; + //else + /// $post .= "[url=".$attachment->url."][img]".$attachment->image->url."[/img][/url]\n"; + + ///$post .= "[quote]".trim(html2bbcode($attachment->content))."[/quote]"; + break; + + case "article": + //$post .= "\n\n[url=".$attachment->url."]". + // "[size=large][b]".html2bbcode($attachment->displayName)."[/b][/size][/url]\n"; + $post .= "\n\n[bookmark=".$attachment->url."]".html2bbcode($attachment->displayName)."[/bookmark]\n"; + $post .= "[quote]".trim(html2bbcode($attachment->content))."[/quote]"; + break; + + case "photo": + //$post .= "\n\n[url=".$attachment->fullImage->url."]". + // "[img]".$attachment->fullImage->url."[/img][/url]\n"; + $post .= "\n\n[img]".$attachment->fullImage->url."[/img]\n"; + if ($attachment->displayName != "") + $post .= html2bbcode($attachment->displayName)."\n"; + break; + + case "photo-album": + $post .= "\n\n[url=".$attachment->url."]". + "[size=large][b]".html2bbcode($attachment->displayName)."[/b][/size][/url]\n"; + break; + + default: + print_r($attachment); + die(); + break; + } + } + return($post); +} + +$result = file_get_contents("https://www.googleapis.com/plus/v1/people/".$google["id"]."/activities/public?alt=json&pp=1&key=".$google["key"]."&maxResults=".$google["maxfetch"]); +$activities = json_decode($result); + +$state = array("lastid"=>''); +if (file_exists($statefile)) + $state = unserialize(file_get_contents($statefile)); + +$lastid = ""; + +foreach($activities->items as $item) { + if ($item->id == $state["lastid"]) + break; + + if ($lastid == "") + $lastid = $item->id; + + switch($item->object->objectType) { + case "note": + $post = html2bbcode($item->object->content); + + if (is_array($item->object->attachments)) + $post .= handleattachments($item); + friendicapost($post); + break; + + case "activity": + $post = html2bbcode($item->annotation)."\n"; + //$post .= html2bbcode("♲ "); + $post .= html2bbcode("♻ "); + $post .= "[url=".$item->object->actor->url."]".$item->object->actor->displayName."[/url]"; + $post .= " \n"; + //$post .= "[quote]"; + + $post .= html2bbcode($item->object->content); + + if (is_array($item->object->attachments)) + $post .= "\n".trim(handleattachments($item)); + + //$post .= "[/quote]"; + + friendicapost($post); + break; + + default: + print_r($item); + die(); + break; + } +} + +if ($lastid != "") { + $state['lastid'] = $lastid; + file_put_contents($statefile, serialize($state)); +} +*/ diff --git a/fromgplus/tofriendica.php b/fromgplus/tofriendica.php new file mode 100644 index 00000000..b185ecc1 --- /dev/null +++ b/fromgplus/tofriendica.php @@ -0,0 +1,128 @@ +", ""), array("[b]", "[/b]"), $bbcode); + $bbcode = str_replace(array("", ""), array("[i]", "[/i]"), $bbcode); + $bbcode = str_replace(array("", ""), array("[s]", "[/s]"), $bbcode); + $bbcode = str_replace(array("
"), array("\n"), $bbcode); + + $bbcode = trim(strip_tags($bbcode)); + return($bbcode); +} + +function friendicapost($post) { + global $friendica; + + $api = new Statusnet($friendica["user"], $friendica["pw"], "GooglePlus", $friendica["server"]); + $ret = $api->updateStatus($post); + $api->endSession(); +} + +function handleattachments($item) { + $post = ""; + + foreach ($item->object->attachments as $attachment) { + switch($attachment->objectType) { + case "video": + //$post .= "\n\n[url=".$attachment->url."]". + // "[size=large][b]".html2bbcode($attachment->displayName)."[/b][/size][/url]\n"; + $post .= "\n\n[bookmark=".$attachment->url."]".html2bbcode($attachment->displayName)."[/bookmark]\n"; + + //if (strpos($attachment->embed->url, "youtube.com")) + // $post .= "[youtube]".$attachment->url."[/youtube]\n"; + //else + /// $post .= "[url=".$attachment->url."][img]".$attachment->image->url."[/img][/url]\n"; + + ///$post .= "[quote]".trim(html2bbcode($attachment->content))."[/quote]"; + break; + + case "article": + //$post .= "\n\n[url=".$attachment->url."]". + // "[size=large][b]".html2bbcode($attachment->displayName)."[/b][/size][/url]\n"; + $post .= "\n\n[bookmark=".$attachment->url."]".html2bbcode($attachment->displayName)."[/bookmark]\n"; + $post .= "[quote]".trim(html2bbcode($attachment->content))."[/quote]"; + break; + + case "photo": + //$post .= "\n\n[url=".$attachment->fullImage->url."]". + // "[img]".$attachment->fullImage->url."[/img][/url]\n"; + $post .= "\n\n[img]".$attachment->fullImage->url."[/img]\n"; + if ($attachment->displayName != "") + $post .= html2bbcode($attachment->displayName)."\n"; + break; + + case "photo-album": + $post .= "\n\n[url=".$attachment->url."]". + "[size=large][b]".html2bbcode($attachment->displayName)."[/b][/size][/url]\n"; + break; + + default: + print_r($attachment); + die(); + break; + } + } + return($post); +} + +$result = file_get_contents("https://www.googleapis.com/plus/v1/people/".$google["id"]."/activities/public?alt=json&pp=1&key=".$google["key"]."&maxResults=".$google["maxfetch"]); +$activities = json_decode($result); + +$state = array("lastid"=>''); +if (file_exists($statefile)) + $state = unserialize(file_get_contents($statefile)); + +$lastid = ""; + +foreach($activities->items as $item) { + if ($item->id == $state["lastid"]) + break; + + if ($lastid == "") + $lastid = $item->id; + + switch($item->object->objectType) { + case "note": + $post = html2bbcode($item->object->content); + + if (is_array($item->object->attachments)) + $post .= handleattachments($item); + friendicapost($post); + break; + + case "activity": + $post = html2bbcode($item->annotation)."\n"; + //$post .= html2bbcode("♲ "); + $post .= html2bbcode("♻ "); + $post .= "[url=".$item->object->actor->url."]".$item->object->actor->displayName."[/url]"; + $post .= " \n"; + //$post .= "[quote]"; + + $post .= html2bbcode($item->object->content); + + if (is_array($item->object->attachments)) + $post .= "\n".trim(handleattachments($item)); + + //$post .= "[/quote]"; + + friendicapost($post); + break; + + default: + print_r($item); + die(); + break; + } +} + +if ($lastid != "") { + $state['lastid'] = $lastid; + file_put_contents($statefile, serialize($state)); +} +?> From 6f27beb728c444c908fdfbea818c612b2ae01a74 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Tue, 10 Apr 2012 23:09:06 +0200 Subject: [PATCH 05/96] Facebook: Don't add "story". Convpath now converts only pictures --- convpath/convpath.php | 7 ++++++- facebook/facebook.php | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/convpath/convpath.php b/convpath/convpath.php index 5ad7fbcb..e5dc0b36 100644 --- a/convpath/convpath.php +++ b/convpath/convpath.php @@ -45,6 +45,11 @@ function convpath_convert($path) { $search = "https://".$a->get_hostname(); $replace = "http://".$a->get_hostname(); } - $path = str_replace($search, $replace, $path); + $searcharr = array("src='".$search, 'src="'.$search); + $replacearr = array("src='".$replace, 'src="'.$replace); + $path = str_replace($searcharr, $replacearr, $path); + + //$path = str_replace($search, $replace, $path); + return($path); } diff --git a/facebook/facebook.php b/facebook/facebook.php index 5990a99e..1e961fda 100755 --- a/facebook/facebook.php +++ b/facebook/facebook.php @@ -1423,10 +1423,19 @@ function fb_consume_stream($uid,$j,$wall = false) { } } + if (($datarray['app'] == "Events") and $entry->actions) + foreach ($entry->actions as $action) + if ($action->name == "View") + $datarray['body'] .= " [url=".$action->link."]".$entry->story."[/url]"; + // Just as a test - to see if these are the missing entries //if(trim($datarray['body']) == '') // $datarray['body'] = $entry->story; + // Adding the "story" text to see if there are useful data in it (testing) + //if (($datarray['app'] != "Events") and $entry->story) + // $datarray['body'] .= "\n".$entry->story; + if(trim($datarray['body']) == '') { logger('facebook: empty body '.$entry->id.' '.print_r($entry, true)); continue; From 40ce1054b77fc4df2c9840ffdf04bb32b35996c0 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Thu, 12 Apr 2012 16:39:36 +0200 Subject: [PATCH 06/96] Facebook: Images weren't posted in some cases --- facebook/facebook.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/facebook/facebook.php b/facebook/facebook.php index b6e40654..6b9ee736 100755 --- a/facebook/facebook.php +++ b/facebook/facebook.php @@ -558,6 +558,9 @@ function facebook_content(&$a) { function facebook_cron($a,$b) { +//del_config('facebook', 'realtime_active'); +//del_config('facebook', 'realtime_err_mailsent'); +//del_config('facebook', 'cb_verify_token'); $last = get_config('facebook','last_poll'); @@ -896,7 +899,7 @@ function facebook_post_hook(&$a,&$b) { if(preg_match("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/is",$b['body'],$matches)) $image = $matches[3]; - if ($image != '') + if ($image == '') if(preg_match("/\[img\](.*?)\[\/img\]/is",$b['body'],$matches)) $image = $matches[1]; @@ -1436,8 +1439,8 @@ function fb_consume_stream($uid,$j,$wall = false) { // $datarray['body'] = $entry->story; // Adding the "story" text to see if there are useful data in it (testing) - //if (($datarray['app'] != "Events") and $entry->story) - // $datarray['body'] .= "\n".$entry->story; + if (($datarray['app'] != "Events") and $entry->story) + $datarray['body'] .= "\n".$entry->story; if(trim($datarray['body']) == '') { logger('facebook: empty body '.$entry->id.' '.print_r($entry, true)); From 977ac72f66fbbb23699767228e67c6051bc5f11e Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Fri, 13 Apr 2012 08:32:17 +0200 Subject: [PATCH 07/96] Facebook: Messages without images and links but with a subject and a text larger than 500 characters will be posted as notes. --- facebook/facebook.php | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/facebook/facebook.php b/facebook/facebook.php index 6b9ee736..a90445db 100755 --- a/facebook/facebook.php +++ b/facebook/facebook.php @@ -1009,10 +1009,14 @@ function facebook_post_hook(&$a,&$b) { 'access_token' => $fb_token, 'message' => $msg ); - if(isset($image)) + if(isset($image)) { $postvars['picture'] = $image; - if(isset($link)) + //$postvars['type'] = "photo"; + } + if(isset($link)) { $postvars['link'] = $link; + //$postvars['type'] = "link"; + } if(isset($linkname)) $postvars['name'] = $linkname; } @@ -1029,11 +1033,18 @@ function facebook_post_hook(&$a,&$b) { if($reply) { $url = 'https://graph.facebook.com/' . $reply . '/' . (($likes) ? 'likes' : 'comments'); - } - else { + } else if (($link != "") or ($image != "") or ($b['title'] == '') or (strlen($msg) < 500)) { $url = 'https://graph.facebook.com/me/feed'; if($b['plink']) $postvars['actions'] = '{"name": "' . t('View on Friendica') . '", "link": "' . $b['plink'] . '"}'; + } else { + // if its only a message and a subject and the message is larger than 500 characters then post it as note + $postvars = array( + 'access_token' => $fb_token, + 'message' => bbcode($b['body']), + 'subject' => $b['title'], + ); + $url = 'https://graph.facebook.com/me/notes'; } logger('facebook: post to ' . $url); From 60a7952688aa41c25106b2a05cad1f9ecf223e48 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Fri, 13 Apr 2012 17:05:16 +0200 Subject: [PATCH 08/96] add jappixmini addon --- jappixmini/README | 23 +++ jappixmini/jappixmini.php | 382 ++++++++++++++++++++++++++++++++++++++ jappixmini/lib.js | 120 ++++++++++++ 3 files changed, 525 insertions(+) create mode 100644 jappixmini/README create mode 100644 jappixmini/jappixmini.php create mode 100644 jappixmini/lib.js diff --git a/jappixmini/README b/jappixmini/README new file mode 100644 index 00000000..2ef7c2f1 --- /dev/null +++ b/jappixmini/README @@ -0,0 +1,23 @@ +Jappix Mini Plugin +================== + +This quick-and-dirty addon allows you to add a Jabber-based, Facebook-like chat +to Friendica. It uses Jappix Mini. + +It is necessary to use a BOSH proxy - so to use this plugin, you need to know the +address of a BOSH proxy that works with your account. + +The addon has an experimental autosubscribe and autosuggest functionality which tries +to add your Friendica contacts to your roster automatically. + +Installation +------------ + +Jappix Mini (AGPL license) is not distributed with this addon. You need to +install it manually: + +* Download latest zip file named friendica-addon-* from + https://github.com/Leberwurscht/jappix-friendica-addon/tags and place the zip file + in the addon folder. Make sure to name it 'jappix.zip' - the download link required + by the AGPL points there. +* Unpack the zip file, rename the folder to 'jappix'. Do not delete jappix.zip. diff --git a/jappixmini/jappixmini.php b/jappixmini/jappixmini.php new file mode 100644 index 00000000..88959448 --- /dev/null +++ b/jappixmini/jappixmini.php @@ -0,0 +1,382 @@ +You need to install the Jappix application, adapted for Friendica (see README).

'; + } + else if (!file_exists("addon/jappixmini/jappix.zip")) { + $o .= '

The source archive jappix.zip does not exist. This is probably a violation of the Jappix License (see README).

'; + } +} + +function jappixmini_plugin_admin_post(&$a) { +} + +function jappixmini_module() {} +function jappixmini_init(&$a) { + // Here, other friendica sites can fetch the jabber address of local users. + // Because we do not want to publish the addresses publicly, they are encrypted so + // that only contacts can read it. + $encrypt_for = $_REQUEST["encrypt_for"]; + if ($encrypt_for) { + $r = q("SELECT * FROM `contact` WHERE LENGTH(`pubkey`) AND `dfrn-id` = '%s' LIMIT 1", + dbesc($encrypt_for) + ); + if (!count($r)) killme(); + + // get public key to encrypt address + $pubkey = $r[0]['pubkey']; + + // get jabber address + $uid = $r[0]['uid']; + $username = get_pconfig($uid, 'jappixmini', 'username'); + if (!$username) killme(); + $server = get_pconfig($uid, 'jappixmini', 'server'); + if (!$server) killme(); + + $address = $username."@".$server; + + // encrypt address + $encrypted = ""; + openssl_public_encrypt($address,$encrypted,$pubkey); + + // calculate hex representation of encrypted address + $hex = bin2hex($encrypted); + + // construct answer + $answer = Array("status"=>"ok", "encrypted_address"=>$hex); + + // return answer as json + echo json_encode($answer); + killme(); + } + + // If we have only a private key, other site sends encrypted request, we answer unencrypted. + $encrypted_for = $_REQUEST["encrypted_for"]; + if (!$encrypted_for) killme(); + + $encrypted_request_hex = $_REQUEST["encrypted_request"]; + if (!$encrypted_request_hex) killme(); + $encrypted_request = hex2bin($encrypted_request_hex); + + $r = q("SELECT * FROM `contact` WHERE LENGTH(`prvkey`) AND `issued-id` = '%s' LIMIT 1", + dbesc($encrypted_for) + ); + if (!count($r)) killme(); + + // decrypt request, validate it + $prvkey = $r[0]['prvkey']; + $decrypted_request = ""; + openssl_private_decrypt($encrypted_request, $decrypted_request, $prvkey); + + if ($decrypted_request!=$encrypted_for) killme(); + + // get jabber address + $uid = $r[0]['uid']; + $username = get_pconfig($uid, 'jappixmini', 'username'); + if (!$username) killme(); + $server = get_pconfig($uid, 'jappixmini', 'server'); + if (!$server) killme(); + + $address = $username."@".$server; + + // construct answer + $answer = Array("status"=>"ok", "address"=>$address); + + // return answer as json + echo json_encode($answer); + killme(); +} + +function jappixmini_settings(&$a, &$s) { + $username = get_pconfig(local_user(),'jappixmini','username'); + $username = htmlentities($username); + $server = get_pconfig(local_user(),'jappixmini','server'); + $server = htmlentities($server); + $bosh = get_pconfig(local_user(),'jappixmini','bosh'); + $bosh = htmlentities($bosh); + $encrypted_password = get_pconfig(local_user(),'jappixmini','encrypted-password'); + $autosubscribe = get_pconfig(local_user(),'jappixmini','autosubscribe'); + $autosubscribe = intval($autosubscribe) ? ' checked="checked"' : ''; + $autoapprove = get_pconfig(local_user(),'jappixmini','autoapprove'); + $autoapprove = intval($autoapprove) ? ' checked="checked"' : ''; + $activate = get_pconfig(local_user(),'jappixmini','activate'); + $activate = intval($activate) ? ' checked="checked"' : ''; + + $s .= '
'; + $s .= '

Jappix Mini addon settings

'; + $s .= '
'; + $s .= ''; + $s .= ' '; + $s .= '
'; + $s .= ''; + $s .= ' '; + $s .= '
'; + $s .= ''; + $s .= ' '; + $s .= '
'; + $s .= ''; + $s .= ' '; + $s .= '
'; + $s .= ''; + $s .= ' '; + $onchange = "document.getElementById('jappixmini-encrypted-password').value = jappixmini_addon_encrypt_password(document.getElementById('jappixmini-password').value);"; + $s .= ' '; + $s .= '
'; + $s .= ''; + $s .= ' '; + $s .= '
'; + $s .= ''; + $s .= ' '; + $s .= '
'; + $s .= ''; + $s .= ' '; + $s .= '
'; + $s .= ''; + $s .= '
'; + + $a->page['htmlhead'] .= ""; +} + +function jappixmini_settings_post(&$a,&$b) { + if(! local_user()) return; + + if($_POST['jappixmini-submit']) { + set_pconfig(local_user(),'jappixmini','username',trim($b['jappixmini-username'])); + set_pconfig(local_user(),'jappixmini','server',trim($b['jappixmini-server'])); + set_pconfig(local_user(),'jappixmini','bosh',trim($b['jappixmini-bosh'])); + set_pconfig(local_user(),'jappixmini','encrypted-password',trim($b['jappixmini-encrypted-password'])); + set_pconfig(local_user(),'jappixmini','autosubscribe',intval($b['jappixmini-autosubscribe'])); + set_pconfig(local_user(),'jappixmini','autoapprove',intval($b['jappixmini-autoapprove'])); + set_pconfig(local_user(),'jappixmini','activate',intval($b['jappixmini-activate'])); + info( 'Jappix Mini settings saved.' ); + + if (intval($b['jappixmini-purge'])) { + $uid = local_user(); + q("DELETE FROM `pconfig` WHERE `uid`=$uid AND `cat`='jappixmini' and `k` LIKE 'id%%'"); + info( 'List of addresses purged.' ); + } + } +} + +function jappixmini_script(&$a,&$s) { + if(! local_user()) return; + + $activate = get_pconfig(local_user(),'jappixmini','activate'); + if (!$activate) return; + + $a->page['htmlhead'] .= ''."\r\n"; + $a->page['htmlhead'] .= ''."\r\n"; + + $a->page['htmlhead'] .= ''."\r\n"; + $a->page['htmlhead'] .= ''."\r\n"; + + $a->page['htmlhead'] .= ''."\r\n"; + + $username = get_pconfig(local_user(),'jappixmini','username'); + $username = str_replace("'", "\\'", $username); + $server = get_pconfig(local_user(),'jappixmini','server'); + $server = str_replace("'", "\\'", $server); + $bosh = get_pconfig(local_user(),'jappixmini','bosh'); + $bosh = str_replace("'", "\\'", $bosh); + $encrypted_password = get_pconfig(local_user(),'jappixmini','encrypted-password'); + $encrypted_password = str_replace("'", "\\'", $encrypted_password); + + $autoapprove = get_pconfig(local_user(),'jappixmini','autoapprove'); + $autoapprove = intval($autoapprove); + $autosubscribe = get_pconfig(local_user(),'jappixmini','autosubscribe'); + $autosubscribe = intval($autosubscribe); + + // get a list of jabber accounts of the contacts + $contacts = Array(); + $uid = local_user(); + $rows = q("SELECT `v` FROM `pconfig` WHERE `uid`=$uid AND `cat`='jappixmini' and `k` LIKE 'id%%'"); + foreach ($rows as $row) { + $value = $row['v']; + $pos = strpos($value, ":"); + $address = substr($value, $pos+1); + $contacts[] = $address; + } + $contacts_json = json_encode($contacts); + + $a->page['htmlhead'] .= ""; + + return; +} + +function jappixmini_login(&$a, &$o) { + // save hash of password using setDB + $o = str_replace("
status != "ok") continue; + + $encrypted_address_hex = $answer->encrypted_address; + if (!$encrypted_address_hex) continue; + $encrypted_address = hex2bin($encrypted_address_hex); + + $decrypted_address = ""; + openssl_private_decrypt($encrypted_address, $decrypted_address, $prvkey); + if (!$decrypted_address) continue; + + $address = $decrypted_address; + } else if ($pubkey) { + $encrypted_request = ""; + openssl_public_encrypt($dfrn_id, $encrypted_request, $pubkey); + if (!$encrypted_request) continue; + $encrypted_request_hex = bin2hex($encrypted_request); + + $retrieval_address = $base."?encrypted_for=".urlencode($dfrn_id)."&encrypted_request=".urlencode($encrypted_request_hex); + + $answer_json = fetch_url($retrieval_address); + $answer = json_decode($answer_json); + if ($answer->status != "ok") continue; + + $address = $answer->address; + if (!$address) continue; + } + + // save address + set_pconfig($uid, "jappixmini", "id:$dfrn_id", "$now:$address"); + } + } +} + +function jappixmini_download_source(&$a,&$b) { + $b .= '

Jappix Mini

'; + $b .= '

This site uses Jappix Mini by the Jappix authors, which is distributed under the terms of the GNU Affero General Public License.

'; + $b .= '

You can download the source code.

'; +} diff --git a/jappixmini/lib.js b/jappixmini/lib.js new file mode 100644 index 00000000..dd95695f --- /dev/null +++ b/jappixmini/lib.js @@ -0,0 +1,120 @@ +function jappixmini_addon_xor(str1, str2) { + if (str1.length != str2.length) throw "not same length"; + + encoded = ""; + + for (i=0; iReintroduce your Friendica password for chatting:
'); + div.append($("
")); + input = $('') + div.append(input); + button = $(''); + button.click(function(){ + password = input.val(); + jappixmini_addon_set_client_secret(password); + div.remove(); + }); + div.append(button); + $("body").append(div); + } + + return client_secret; +} + +function jappixmini_addon_encrypt_password(password) { + client_secret = jappixmini_addon_get_client_secret(); + + // add \0 to password until it has the same length as secret + if (client_secret.length Date: Fri, 13 Apr 2012 18:53:32 +0200 Subject: [PATCH 09/96] new download path in jappixmini addon README --- jappixmini/README | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jappixmini/README b/jappixmini/README index 2ef7c2f1..cf9eb70b 100644 --- a/jappixmini/README +++ b/jappixmini/README @@ -17,7 +17,7 @@ Jappix Mini (AGPL license) is not distributed with this addon. You need to install it manually: * Download latest zip file named friendica-addon-* from - https://github.com/Leberwurscht/jappix-friendica-addon/tags and place the zip file - in the addon folder. Make sure to name it 'jappix.zip' - the download link required - by the AGPL points there. + https://github.com/Leberwurscht/jappix/tags and place the zip file in the addon + folder. Make sure to name it 'jappix.zip' - the download link required by the AGPL + points there. * Unpack the zip file, rename the folder to 'jappix'. Do not delete jappix.zip. From af99d51a7cc8916c3fe9802a25f82a64b4b3b526 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Sat, 14 Apr 2012 18:23:42 +0200 Subject: [PATCH 10/96] bug fixes and improvements --- jappixmini/README | 3 + jappixmini/jappixmini.php | 285 +++++++++++++++++++++++--------------- jappixmini/lib.js | 28 ++-- 3 files changed, 196 insertions(+), 120 deletions(-) diff --git a/jappixmini/README b/jappixmini/README index cf9eb70b..bd1e2aa1 100644 --- a/jappixmini/README +++ b/jappixmini/README @@ -10,6 +10,9 @@ address of a BOSH proxy that works with your account. The addon has an experimental autosubscribe and autosuggest functionality which tries to add your Friendica contacts to your roster automatically. +Limitations: + - can only handle Jabber passwords that are at most 39 characters long + Installation ------------ diff --git a/jappixmini/jappixmini.php b/jappixmini/jappixmini.php index 88959448..08ba248d 100644 --- a/jappixmini/jappixmini.php +++ b/jappixmini/jappixmini.php @@ -23,7 +23,8 @@ and not to the server (at least as soon as the user is logged in). It can be sto This encryption key could be the friendica password, but then this password would be stored in the browser in cleartext. It is better to use a hash of the password. The server should not be able to reconstruct the password, so we can't take the same hash the server stores. But we can - use hash("some_prefix"+password). This will however not work with OpenID logins. + use hash("some_prefix"+password). This will however not work with OpenID logins, for this type of login the password must +be queried manually. Problem: How to discover the jabber addresses of the friendica contacts? @@ -37,17 +38,23 @@ We do not want to make the jabber address public. Solution: When two friendica users connect using DFRN, the relation gets a DFRN ID and a keypair is generated. -Using this keypair, we can provide the jabber address only to contacs: +Using this keypair, we can provide the jabber address only to contacts: -Case 1: Alice has prvkey, Bob has pubkey. - Alice encrypts request - Bob decrypts the request, send jabber address unencrypted - Alice reads address +Alice: + signed_address = openssl_*_encrypt(alice_jabber_address) +send signed_address to Bob, who does + trusted_address = openssl_*_decrypt(signed_address) + save trusted_address + encrypted_address = openssl_*_encrypt(bob_jabber_address) +reply with encrypted_address to Alice, who does + decrypted_address = openssl_*_decrypt(encrypted_address) + save decrypted_address -Case 2: Alice has prvkey, Bob has pubkey - Alice send request - Bob encrypts jabber address - Alice decrypts jabber address +Interface for this: +GET /jappixmini/?role=%s&signed_address=%s&dfrn_id=%s + +Response: +json({"status":"ok", "encrypted_address":"%s"}) */ @@ -78,6 +85,8 @@ unregister_hook('about_hook', 'addon/jappixmini/jappixmini.php', 'jappixmini_dow } function jappixmini_plugin_admin(&$a, &$o) { + // display instructions and warnings on addon settings page for admin + if (!file_exists("addon/jappixmini/jappix")) { $o .= '

You need to install the Jappix application, adapted for Friendica (see README).

'; } @@ -91,81 +100,80 @@ function jappixmini_plugin_admin_post(&$a) { function jappixmini_module() {} function jappixmini_init(&$a) { - // Here, other friendica sites can fetch the jabber address of local users. - // Because we do not want to publish the addresses publicly, they are encrypted so - // that only contacts can read it. - $encrypt_for = $_REQUEST["encrypt_for"]; - if ($encrypt_for) { - $r = q("SELECT * FROM `contact` WHERE LENGTH(`pubkey`) AND `dfrn-id` = '%s' LIMIT 1", - dbesc($encrypt_for) + // module page where other Friendica sites can submit Jabber addresses to and also can query Jabber addresses + // of local users + + $dfrn_id = $_REQUEST["dfrn_id"]; + if (!$dfrn_id) killme(); + + $role = $_REQUEST["role"]; + if ($role=="pub") { + $r = q("SELECT * FROM `contact` WHERE LENGTH(`pubkey`) AND `dfrn-id`='%s' LIMIT 1", + dbesc($dfrn_id) ); if (!count($r)) killme(); - // get public key to encrypt address - $pubkey = $r[0]['pubkey']; + $encrypt_func = openssl_public_encrypt; + $decrypt_func = openssl_public_decrypt; + $key = $r[0]["pubkey"]; + } else if ($role=="prv") { + $r = q("SELECT * FROM `contact` WHERE LENGTH(`prvkey`) AND `issued-id`='%s' LIMIT 1", + dbesc($dfrn_id) + ); + if (!count($r)) killme(); - // get jabber address - $uid = $r[0]['uid']; - $username = get_pconfig($uid, 'jappixmini', 'username'); - if (!$username) killme(); - $server = get_pconfig($uid, 'jappixmini', 'server'); - if (!$server) killme(); - - $address = $username."@".$server; - - // encrypt address - $encrypted = ""; - openssl_public_encrypt($address,$encrypted,$pubkey); - - // calculate hex representation of encrypted address - $hex = bin2hex($encrypted); - - // construct answer - $answer = Array("status"=>"ok", "encrypted_address"=>$hex); - - // return answer as json - echo json_encode($answer); + $encrypt_func = openssl_private_encrypt; + $decrypt_func = openssl_private_decrypt; + $key = $r[0]["prvkey"]; + } else { killme(); } - // If we have only a private key, other site sends encrypted request, we answer unencrypted. - $encrypted_for = $_REQUEST["encrypted_for"]; - if (!$encrypted_for) killme(); + $uid = $r[0]["uid"]; - $encrypted_request_hex = $_REQUEST["encrypted_request"]; - if (!$encrypted_request_hex) killme(); - $encrypted_request = hex2bin($encrypted_request_hex); + // save the Jabber address we received + try { + $signed_address_hex = $_REQUEST["signed_address"]; + $signed_address = hex2bin($signed_address_hex); - $r = q("SELECT * FROM `contact` WHERE LENGTH(`prvkey`) AND `issued-id` = '%s' LIMIT 1", - dbesc($encrypted_for) - ); - if (!count($r)) killme(); + $trusted_address = ""; + $decrypt_func($signed_address, $trusted_address, $key); - // decrypt request, validate it - $prvkey = $r[0]['prvkey']; - $decrypted_request = ""; - openssl_private_decrypt($encrypted_request, $decrypted_request, $prvkey); + $now = intval(time()); + set_pconfig($uid, "jappixmini", "id:$dfrn_id", "$now:$trusted_address"); + } catch (Exception $e) { + } - if ($decrypted_request!=$encrypted_for) killme(); + // return the requested Jabber address + try { + $username = get_pconfig($uid, 'jappixmini', 'username'); + $server = get_pconfig($uid, 'jappixmini', 'server'); + $address = "$username@$server"; - // get jabber address - $uid = $r[0]['uid']; - $username = get_pconfig($uid, 'jappixmini', 'username'); - if (!$username) killme(); - $server = get_pconfig($uid, 'jappixmini', 'server'); - if (!$server) killme(); + $encrypted_address = ""; + $encrypt_func($address, $encrypted_address, $key); - $address = $username."@".$server; + $encrypted_address_hex = bin2hex($encrypted_address); - // construct answer - $answer = Array("status"=>"ok", "address"=>$address); + $answer = Array( + "status"=>"ok", + "encrypted_address"=>$encrypted_address_hex + ); - // return answer as json - echo json_encode($answer); - killme(); + $answer_json = json_encode($answer); + echo $answer_json; + killme(); + } catch (Exception $e) { + killme(); + } } function jappixmini_settings(&$a, &$s) { + // addon settings for a user + + $activate = get_pconfig(local_user(),'jappixmini','activate'); + $activate = intval($activate) ? ' checked="checked"' : ''; + $username = get_pconfig(local_user(),'jappixmini','username'); $username = htmlentities($username); $server = get_pconfig(local_user(),'jappixmini','server'); @@ -177,8 +185,6 @@ function jappixmini_settings(&$a, &$s) { $autosubscribe = intval($autosubscribe) ? ' checked="checked"' : ''; $autoapprove = get_pconfig(local_user(),'jappixmini','autoapprove'); $autoapprove = intval($autoapprove) ? ' checked="checked"' : ''; - $activate = get_pconfig(local_user(),'jappixmini','activate'); - $activate = intval($activate) ? ' checked="checked"' : ''; $s .= '
'; $s .= '

Jappix Mini addon settings

'; @@ -206,7 +212,7 @@ function jappixmini_settings(&$a, &$s) { $s .= ''; $s .= ' '; $s .= '
'; - $s .= ''; + $s .= ''; $s .= ' '; $s .= '
'; $s .= ''; @@ -220,20 +226,32 @@ function jappixmini_settings(&$a, &$s) { } function jappixmini_settings_post(&$a,&$b) { + // save addon settings for a user + if(! local_user()) return; + $uid = local_user(); if($_POST['jappixmini-submit']) { - set_pconfig(local_user(),'jappixmini','username',trim($b['jappixmini-username'])); - set_pconfig(local_user(),'jappixmini','server',trim($b['jappixmini-server'])); - set_pconfig(local_user(),'jappixmini','bosh',trim($b['jappixmini-bosh'])); - set_pconfig(local_user(),'jappixmini','encrypted-password',trim($b['jappixmini-encrypted-password'])); - set_pconfig(local_user(),'jappixmini','autosubscribe',intval($b['jappixmini-autosubscribe'])); - set_pconfig(local_user(),'jappixmini','autoapprove',intval($b['jappixmini-autoapprove'])); - set_pconfig(local_user(),'jappixmini','activate',intval($b['jappixmini-activate'])); + $purge = intval($b['jappixmini-purge']); + + $username = trim($b['jappixmini-username']); + $old_username = get_pconfig($uid,'jappixmini','username'); + if ($username!=$old_username) $purge = 1; + + $server = trim($b['jappixmini-server']); + $old_server = get_pconfig($uid,'jappixmini','server'); + if ($server!=$old_server) $purge = 1; + + set_pconfig($uid,'jappixmini','username',$username); + set_pconfig($uid,'jappixmini','server',$server); + set_pconfig($uid,'jappixmini','bosh',trim($b['jappixmini-bosh'])); + set_pconfig($uid,'jappixmini','encrypted-password',trim($b['jappixmini-encrypted-password'])); + set_pconfig($uid,'jappixmini','autosubscribe',intval($b['jappixmini-autosubscribe'])); + set_pconfig($uid,'jappixmini','autoapprove',intval($b['jappixmini-autoapprove'])); + set_pconfig($uid,'jappixmini','activate',intval($b['jappixmini-activate'])); info( 'Jappix Mini settings saved.' ); - if (intval($b['jappixmini-purge'])) { - $uid = local_user(); + if ($purge) { q("DELETE FROM `pconfig` WHERE `uid`=$uid AND `cat`='jappixmini' and `k` LIKE 'id%%'"); info( 'List of addresses purged.' ); } @@ -241,6 +259,8 @@ function jappixmini_settings_post(&$a,&$b) { } function jappixmini_script(&$a,&$s) { + // adds the script to the page header which starts Jappix Mini + if(! local_user()) return; $activate = get_pconfig(local_user(),'jappixmini','activate'); @@ -251,6 +271,7 @@ function jappixmini_script(&$a,&$s) { $a->page['htmlhead'] .= ''."\r\n"; $a->page['htmlhead'] .= ''."\r\n"; + $a->page['htmlhead'] .= ''."\r\n"; $a->page['htmlhead'] .= ''."\r\n"; @@ -271,18 +292,35 @@ function jappixmini_script(&$a,&$s) { // get a list of jabber accounts of the contacts $contacts = Array(); $uid = local_user(); - $rows = q("SELECT `v` FROM `pconfig` WHERE `uid`=$uid AND `cat`='jappixmini' and `k` LIKE 'id%%'"); + $rows = q("SELECT * FROM `pconfig` WHERE `uid`=$uid AND `cat`='jappixmini' and `k` LIKE 'id%%'"); foreach ($rows as $row) { + $key = $row['k']; + $pos = strpos($key, ":"); + $dfrn_id = substr($key, $pos+1); + $r = q("SELECT `name` FROM `contact` WHERE `uid`=$uid AND `dfrn-id`='%s' OR `issued-id`='%s'", + dbesc($dfrn_id), + dbesc($dfrn_id) + ); + $name = $r[0]["name"]; + $value = $row['v']; $pos = strpos($value, ":"); $address = substr($value, $pos+1); - $contacts[] = $address; + if (!$address) continue; + if (!$name) $name = $address; + + $contacts[$address] = $name; } $contacts_json = json_encode($contacts); + // get nickname + $r = q("SELECT `username` FROM `user` WHERE `uid`=$uid"); + $nickname = json_encode($r[0]["username"]); + + // add javascript to start Jappix Mini $a->page['htmlhead'] .= ""; @@ -291,7 +329,16 @@ function jappixmini_script(&$a,&$s) { } function jappixmini_login(&$a, &$o) { - // save hash of password using setDB + // for setDB, needed by jappixmini_addon_set_client_secret + $a->page['htmlhead'] .= ''."\r\n"; + + // for str_sha1, needed by jappixmini_addon_set_client_secret + $a->page['htmlhead'] .= ''."\r\n"; + + // for jappixmini_addon_set_client_secret + $a->page['htmlhead'] .= ''."\r\n"; + + // save hash of password $o = str_replace("status != "ok") continue; + if ($answer->status != "ok") throw new Exception(); $encrypted_address_hex = $answer->encrypted_address; - if (!$encrypted_address_hex) continue; + if (!$encrypted_address_hex) throw new Exception(); + $encrypted_address = hex2bin($encrypted_address_hex); + if (!$encrypted_address) throw new Exception(); + // decrypt address + $decrypted_address = ""; + $decrypt_func($encrypted_address, $decrypted_address, $key); + if (!$decrypted_address) throw new Exception(); + } catch (Exception $e) { $decrypted_address = ""; - openssl_private_decrypt($encrypted_address, $decrypted_address, $prvkey); - if (!$decrypted_address) continue; - - $address = $decrypted_address; - } else if ($pubkey) { - $encrypted_request = ""; - openssl_public_encrypt($dfrn_id, $encrypted_request, $pubkey); - if (!$encrypted_request) continue; - $encrypted_request_hex = bin2hex($encrypted_request); - - $retrieval_address = $base."?encrypted_for=".urlencode($dfrn_id)."&encrypted_request=".urlencode($encrypted_request_hex); - - $answer_json = fetch_url($retrieval_address); - $answer = json_decode($answer_json); - if ($answer->status != "ok") continue; - - $address = $answer->address; - if (!$address) continue; } // save address - set_pconfig($uid, "jappixmini", "id:$dfrn_id", "$now:$address"); + set_pconfig($uid, "jappixmini", "id:$dfrn_id", "$now:$decrypted_address"); } } } function jappixmini_download_source(&$a,&$b) { + // Jappix Mini source download link on About page + $b .= '

Jappix Mini

'; $b .= '

This site uses Jappix Mini by the Jappix authors, which is distributed under the terms of the GNU Affero General Public License.

'; $b .= '

You can download the source code.

'; diff --git a/jappixmini/lib.js b/jappixmini/lib.js index dd95695f..6ab4644d 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -15,14 +15,23 @@ function jappixmini_addon_xor(str1, str2) { } function jappixmini_addon_set_client_secret(password) { - client_secret = str_sha1("client_secret:"+password); + if (!password) return; + + salt1 = "h8doCRekWto0njyQohKpdx6BN0UTyC6N"; + salt2 = "jdX8OwFC1kWAq3s9uOyAcE8g3UNNO5t3"; + + client_secret1 = str_sha1(salt1+password); + client_secret2 = str_sha1(salt2+password); + client_secret = client_secret1 + client_secret2; + setDB('jappix-mini', 'client_secret', client_secret); + console.log("client secret set"); } function jappixmini_addon_get_client_secret() { client_secret = getDB('jappix-mini', 'client_secret'); if (client_secret===null) { - div = $('
Reintroduce your Friendica password for chatting:
'); + div = $('
Retype your Friendica password for chatting:
'); div.append($("
")); input = $('') div.append(input); @@ -43,7 +52,7 @@ function jappixmini_addon_encrypt_password(password) { client_secret = jappixmini_addon_get_client_secret(); // add \0 to password until it has the same length as secret - if (client_secret.lengthclient_secret.length-1) throw "password too long"; while (password.length Date: Sat, 14 Apr 2012 18:41:11 +0200 Subject: [PATCH 11/96] improve README of jappixmini addon --- jappixmini/README | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/jappixmini/README b/jappixmini/README index bd1e2aa1..7818d41a 100644 --- a/jappixmini/README +++ b/jappixmini/README @@ -4,14 +4,18 @@ Jappix Mini Plugin This quick-and-dirty addon allows you to add a Jabber-based, Facebook-like chat to Friendica. It uses Jappix Mini. -It is necessary to use a BOSH proxy - so to use this plugin, you need to know the -address of a BOSH proxy that works with your account. +It is necessary to use a BOSH proxy - so to use this plugin, you need to know +the address of a BOSH proxy that works with your account. The BOSH server of +the Jappix project (https://bind.jappix.com/) is not locked to a specific XMPP +provider, but keep in mind that only personal usage is approved according to +http://codingteam.net/project/jappix/doc/BoshServer. -The addon has an experimental autosubscribe and autosuggest functionality which tries -to add your Friendica contacts to your roster automatically. +The addon has an experimental autosubscribe and autosuggest functionality which +tries to add your Friendica contacts to your roster automatically. Limitations: - - can only handle Jabber passwords that are at most 39 characters long + - The addon can only handle Jabber passwords that are at most 39 characters + long. Installation ------------ @@ -20,7 +24,7 @@ Jappix Mini (AGPL license) is not distributed with this addon. You need to install it manually: * Download latest zip file named friendica-addon-* from - https://github.com/Leberwurscht/jappix/tags and place the zip file in the addon - folder. Make sure to name it 'jappix.zip' - the download link required by the AGPL - points there. + https://github.com/Leberwurscht/jappix/tags and place the zip file in the + addon folder. Make sure to name it 'jappix.zip' - the download link required + by the AGPL points there. * Unpack the zip file, rename the folder to 'jappix'. Do not delete jappix.zip. From 011bf47e59f84e2e184b8364a44b9edbdcee6342 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Sat, 14 Apr 2012 18:49:23 +0200 Subject: [PATCH 12/96] add jappixmini/jappix/ and jappixmini/jappix.zip to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..77d48406 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +jappixmini/jappix/ +jappixmini/jappix.zip From c376bc162bf611345d7796e4844e57cc9ae780c5 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Sun, 15 Apr 2012 12:20:53 +0200 Subject: [PATCH 13/96] make bosh proxy optional, add subscribe functionality and some more improvements of jappixmini addon --- jappixmini/README | 9 ++- jappixmini/jappixmini.php | 116 ++++++++++++++++++++++++++++++++++---- jappixmini/lib.js | 24 ++++++-- 3 files changed, 128 insertions(+), 21 deletions(-) diff --git a/jappixmini/README b/jappixmini/README index 7818d41a..822f4391 100644 --- a/jappixmini/README +++ b/jappixmini/README @@ -4,17 +4,20 @@ Jappix Mini Plugin This quick-and-dirty addon allows you to add a Jabber-based, Facebook-like chat to Friendica. It uses Jappix Mini. -It is necessary to use a BOSH proxy - so to use this plugin, you need to know -the address of a BOSH proxy that works with your account. The BOSH server of +It is necessary to use a BOSH host - so to use this plugin, each users need to +know the address of a BOSH host that works with his account. The BOSH server of the Jappix project (https://bind.jappix.com/) is not locked to a specific XMPP provider, but keep in mind that only personal usage is approved according to http://codingteam.net/project/jappix/doc/BoshServer. +If you have a larger server, it is recommended that you install your own BOSH +server, add it to the tag in jappix/store/conf/hosts.xml, and disable +the bosh proxy in jappix/store/conf/main.xml. The addon has an experimental autosubscribe and autosuggest functionality which tries to add your Friendica contacts to your roster automatically. Limitations: - - The addon can only handle Jabber passwords that are at most 39 characters + - Jabber passwords can only be encrypted if they are at most 39 characters long. Installation diff --git a/jappixmini/jappixmini.php b/jappixmini/jappixmini.php index 08ba248d..3e16a6ab 100644 --- a/jappixmini/jappixmini.php +++ b/jappixmini/jappixmini.php @@ -93,6 +93,9 @@ function jappixmini_plugin_admin(&$a, &$o) { else if (!file_exists("addon/jappixmini/jappix.zip")) { $o .= '

The source archive jappix.zip does not exist. This is probably a violation of the Jappix License (see README).

'; } + else { + $o .= '

Jappix is installed.

'; + } } function jappixmini_plugin_admin_post(&$a) { @@ -103,6 +106,8 @@ function jappixmini_init(&$a) { // module page where other Friendica sites can submit Jabber addresses to and also can query Jabber addresses // of local users + if (!file_exists("addon/jappixmini/jappix")) killme(); + $dfrn_id = $_REQUEST["dfrn_id"]; if (!$dfrn_id) killme(); @@ -171,6 +176,8 @@ function jappixmini_init(&$a) { function jappixmini_settings(&$a, &$s) { // addon settings for a user + if (!file_exists("addon/jappixmini/jappix")) return; + $activate = get_pconfig(local_user(),'jappixmini','activate'); $activate = intval($activate) ? ' checked="checked"' : ''; @@ -180,11 +187,26 @@ function jappixmini_settings(&$a, &$s) { $server = htmlentities($server); $bosh = get_pconfig(local_user(),'jappixmini','bosh'); $bosh = htmlentities($bosh); - $encrypted_password = get_pconfig(local_user(),'jappixmini','encrypted-password'); + $password = get_pconfig(local_user(),'jappixmini','password'); $autosubscribe = get_pconfig(local_user(),'jappixmini','autosubscribe'); $autosubscribe = intval($autosubscribe) ? ' checked="checked"' : ''; $autoapprove = get_pconfig(local_user(),'jappixmini','autoapprove'); $autoapprove = intval($autoapprove) ? ' checked="checked"' : ''; + $encrypt = intval(get_pconfig(local_user(),'jappixmini','encrypt')); + $encrypt_checked = $encrypt ? ' checked="checked"' : ''; + $encrypt_disabled = $encrypt ? '' : ' disabled="disabled"'; + + if (!$activate) { + // load scripts if not yet activated so that password can be saved + $a->page['htmlhead'] .= ''."\r\n"; + $a->page['htmlhead'] .= ''."\r\n"; + + $a->page['htmlhead'] .= ''."\r\n"; + $a->page['htmlhead'] .= ''."\r\n"; + $a->page['htmlhead'] .= ''."\r\n"; + + $a->page['htmlhead'] .= ''."\r\n"; + } $s .= '
'; $s .= '

Jappix Mini addon settings

'; @@ -198,13 +220,25 @@ function jappixmini_settings(&$a, &$s) { $s .= ''; $s .= ' '; $s .= '
'; - $s .= ''; - $s .= ' '; - $s .= '
'; + + $conf = file_get_contents("addon/jappixmini/jappix/store/conf/main.xml"); + preg_match("/(.*)<\/bosh_proxy>/", $conf, $matches); + if ($matches[1]=="on") { + $s .= ''; + $s .= ' '; + $s .= '
'; + } + $s .= ''; - $s .= ' '; - $onchange = "document.getElementById('jappixmini-encrypted-password').value = jappixmini_addon_encrypt_password(document.getElementById('jappixmini-password').value);"; - $s .= ' '; + $s .= ' '; + $s .= ' '; + $s .= '
'; + $onchange = "document.getElementById('jappixmini-friendica-password').disabled = !this.checked;jappixmini_set_password();"; + $s .= ''; + $s .= ' '; + $s .= '
'; + $s .= ''; + $s .= ' '; $s .= '
'; $s .= ''; $s .= ' '; @@ -216,11 +250,34 @@ function jappixmini_settings(&$a, &$s) { $s .= ' '; $s .= '
'; $s .= ''; + $s .= ' '; $s .= '
'; $a->page['htmlhead'] .= ""; } @@ -228,10 +285,26 @@ function jappixmini_settings(&$a, &$s) { function jappixmini_settings_post(&$a,&$b) { // save addon settings for a user + if (!file_exists("addon/jappixmini/jappix")) return; + if(! local_user()) return; $uid = local_user(); if($_POST['jappixmini-submit']) { + $encrypt = intval($b['jappixmini-encrypt']); + if ($encrypt) { + // check that Jabber password was encrypted with correct Friendica password + $friendica_password = trim($b['jappixmini-friendica-password']); + $encrypted = hash('whirlpool',$friendica_password); + $r = q("SELECT * FROM `user` WHERE `uid`=$uid AND `password`='%s'", + dbesc($encrypted) + ); + if (!count($r)) { + info("Wrong friendica password!"); + return; + } + } + $purge = intval($b['jappixmini-purge']); $username = trim($b['jappixmini-username']); @@ -245,10 +318,11 @@ function jappixmini_settings_post(&$a,&$b) { set_pconfig($uid,'jappixmini','username',$username); set_pconfig($uid,'jappixmini','server',$server); set_pconfig($uid,'jappixmini','bosh',trim($b['jappixmini-bosh'])); - set_pconfig($uid,'jappixmini','encrypted-password',trim($b['jappixmini-encrypted-password'])); + set_pconfig($uid,'jappixmini','password',trim($b['jappixmini-encrypted-password'])); set_pconfig($uid,'jappixmini','autosubscribe',intval($b['jappixmini-autosubscribe'])); set_pconfig($uid,'jappixmini','autoapprove',intval($b['jappixmini-autoapprove'])); set_pconfig($uid,'jappixmini','activate',intval($b['jappixmini-activate'])); + set_pconfig($uid,'jappixmini','encrypt',$encrypt); info( 'Jappix Mini settings saved.' ); if ($purge) { @@ -261,6 +335,7 @@ function jappixmini_settings_post(&$a,&$b) { function jappixmini_script(&$a,&$s) { // adds the script to the page header which starts Jappix Mini + if (!file_exists("addon/jappixmini/jappix")) return; if(! local_user()) return; $activate = get_pconfig(local_user(),'jappixmini','activate'); @@ -281,14 +356,23 @@ function jappixmini_script(&$a,&$s) { $server = str_replace("'", "\\'", $server); $bosh = get_pconfig(local_user(),'jappixmini','bosh'); $bosh = str_replace("'", "\\'", $bosh); - $encrypted_password = get_pconfig(local_user(),'jappixmini','encrypted-password'); - $encrypted_password = str_replace("'", "\\'", $encrypted_password); + $encrypt = get_pconfig(local_user(),'jappixmini','encrypt'); + $encrypt = intval($encrypt); + $password = get_pconfig(local_user(),'jappixmini','password'); + $password = str_replace("'", "\\'", $password); $autoapprove = get_pconfig(local_user(),'jappixmini','autoapprove'); $autoapprove = intval($autoapprove); $autosubscribe = get_pconfig(local_user(),'jappixmini','autosubscribe'); $autosubscribe = intval($autosubscribe); + // deactivate bosh host if proxy is off + $conf = file_get_contents("addon/jappixmini/jappix/store/conf/main.xml"); + preg_match("/(.*)<\/bosh_proxy>/", $conf, $matches); + if ($matches[1]!="on") { + $bosh = ''; + } + // get a list of jabber accounts of the contacts $contacts = Array(); $uid = local_user(); @@ -320,7 +404,7 @@ function jappixmini_script(&$a,&$s) { // add javascript to start Jappix Mini $a->page['htmlhead'] .= ""; @@ -329,6 +413,10 @@ function jappixmini_script(&$a,&$s) { } function jappixmini_login(&$a, &$o) { + // create client secret on login to be able to encrypt jabber passwords + + if (!file_exists("addon/jappixmini/jappix")) return; + // for setDB, needed by jappixmini_addon_set_client_secret $a->page['htmlhead'] .= ''."\r\n"; @@ -345,6 +433,8 @@ function jappixmini_login(&$a, &$o) { function jappixmini_cron(&$a, $d) { // For autosubscribe/autoapprove, we need to maintain a list of jabber addresses of our contacts. + if (!file_exists("addon/jappixmini/jappix")) return; + // go through list of users with jabber enabled $users = q("SELECT `uid` FROM `pconfig` WHERE `cat`='jappixmini' AND (`k`='autosubscribe' OR `k`='autoapprove') AND `v`='1'"); @@ -437,6 +527,8 @@ function jappixmini_cron(&$a, $d) { function jappixmini_download_source(&$a,&$b) { // Jappix Mini source download link on About page + if (!file_exists("addon/jappixmini/jappix")) return; + $b .= '

Jappix Mini

'; $b .= '

This site uses Jappix Mini by the Jappix authors, which is distributed under the terms of the GNU Affero General Public License.

'; $b .= '

You can download the source code.

'; diff --git a/jappixmini/lib.js b/jappixmini/lib.js index 6ab4644d..610a8312 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -108,12 +108,26 @@ function jappixmini_manage_roster(contacts, autoapprove, autosubscribe) { } } -function jappixmini_addon_start(server, username, bosh, encrypted_password, nickname) { +function jappixmini_addon_subscribe() { + if (!window.con) { + alert("Not connected."); + return; + } + + xid = prompt("Jabber address"); + sendSubscribe(xid, "subscribe"); +} + +function jappixmini_addon_start(server, username, bosh, encrypted, password, nickname) { + // decrypt password + if (encrypted) + password = jappixmini_addon_decrypt_password(password); + // check if settings have changed, reinitialize jappix mini if this is the case settings_identifier = str_sha1(server); settings_identifier += str_sha1(username); settings_identifier += str_sha1(bosh); - settings_identifier += str_sha1(encrypted_password); + settings_identifier += str_sha1(password); settings_identifier += str_sha1(nickname); saved_identifier = getDB("jappix-mini", "settings_identifier"); @@ -121,10 +135,8 @@ function jappixmini_addon_start(server, username, bosh, encrypted_password, nick setDB("jappix-mini", "settings_identifier", settings_identifier); // set bosh host - HOST_BOSH = HOST_BOSH+"?host_bosh="+encodeURI(bosh); - - // decrypt password - password = jappixmini_addon_decrypt_password(encrypted_password); + if (bosh) + HOST_BOSH = HOST_BOSH+"?host_bosh="+encodeURI(bosh); // start jappix mini MINI_NICKNAME = nickname; From 3064bf55cbb2111d9b3b4c64fdb9792d6709003a Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Sun, 15 Apr 2012 13:13:05 +0200 Subject: [PATCH 14/96] add jappixmini.tgz --- jappixmini.tgz | Bin 0 -> 7397 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 jappixmini.tgz diff --git a/jappixmini.tgz b/jappixmini.tgz new file mode 100644 index 0000000000000000000000000000000000000000..6420352647672a99ba8968a766d44b3acd6a684b GIT binary patch literal 7397 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 literal 0 HcmV?d00001 From 90289ef561d688844e7929fdb45bc28ebb849d99 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Sun, 15 Apr 2012 13:21:32 +0200 Subject: [PATCH 15/96] add missing
to jappixmini settings --- jappixmini/jappixmini.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jappixmini/jappixmini.php b/jappixmini/jappixmini.php index 3e16a6ab..0e1cc5df 100644 --- a/jappixmini/jappixmini.php +++ b/jappixmini/jappixmini.php @@ -209,6 +209,7 @@ function jappixmini_settings(&$a, &$s) { } $s .= '
'; + $s .= '

Jappix Mini addon settings

'; $s .= '
'; $s .= ''; @@ -253,6 +254,8 @@ function jappixmini_settings(&$a, &$s) { $s .= ' '; $s .= '
'; + $s .= '
'; + $a->page['htmlhead'] .= "'; + } else { + $use = get_pconfig(local_user(),'mathjax','use'); + if ($use) { + $b .= ''; + } + } +} +function mathjax_plugin_admin_post (&$a) { + $baseurl = ((x($_POST, 'baseurl')) ? trim($_POST['baseurl']) : 'http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'); + set_config('mathjax','baseurl',$baseurl); + info( t('Settings updated.'). EOL); +} +function mathjax_plugin_admin (&$a, &$o) { + $t = file_get_contents( dirname(__file__)."/admin.tpl"); + if (get_config('mathjax','baseurl','') == '') { + set_config('mathjax','baseurl','http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'); + } + $o = replace_macros( $t, array( + '$baseurl' => array('baseurl', t('MathJax Base URL'), get_config('mathjax','baseurl' ), t('The URL for the javascript file that should be included to use MathJax. Can be either the MathJax CDN or another installation of MathJax.')), + )); +} From 6034ba3263fcfea911e914b38f6cdac188c7a596 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Sun, 15 Apr 2012 22:57:25 +0200 Subject: [PATCH 20/96] jappixmini: wait for client secret to be set --- jappixmini/jappixmini.php | 8 ++- jappixmini/lib.js | 126 +++++++++++++++++++++----------------- 2 files changed, 77 insertions(+), 57 deletions(-) diff --git a/jappixmini/jappixmini.php b/jappixmini/jappixmini.php index b68114d1..80fc237f 100644 --- a/jappixmini/jappixmini.php +++ b/jappixmini/jappixmini.php @@ -270,7 +270,9 @@ function jappixmini_settings(&$a, &$s) { if (friendica_password) { jappixmini_addon_set_client_secret(friendica_password.value); - password.value = jappixmini_addon_encrypt_password(clear_password.value); + jappixmini_addon_encrypt_password(clear_password.value, function(encrypted_password){ + password.value = encrypted_password; + }); } } else { @@ -283,7 +285,9 @@ function jappixmini_settings(&$a, &$s) { password = document.getElementById('jappixmini-password'); clear_password = document.getElementById('jappixmini-clear-password'); if (encrypt) { - clear_password.value = jappixmini_addon_decrypt_password(password.value); + jappixmini_addon_decrypt_password(password.value, function(decrypted_password){ + clear_password.value = decrypted_password; + }); } else { clear_password.value = password.value; diff --git a/jappixmini/lib.js b/jappixmini/lib.js index 610a8312..94e465d1 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -24,59 +24,71 @@ function jappixmini_addon_set_client_secret(password) { client_secret2 = str_sha1(salt2+password); client_secret = client_secret1 + client_secret2; - setDB('jappix-mini', 'client_secret', client_secret); + setDB('jappix-mini', 'client-secret', client_secret); console.log("client secret set"); } -function jappixmini_addon_get_client_secret() { - client_secret = getDB('jappix-mini', 'client_secret'); +function jappixmini_addon_get_client_secret(callback) { + client_secret = getDB('jappix-mini', 'client-secret'); if (client_secret===null) { - div = $('
Retype your Friendica password for chatting:
'); - div.append($("
")); - input = $('') - div.append(input); - button = $(''); + div = document.getElementById("#jappixmini-password-query-div"); + + if (!div) { + div = $('
Retype your Friendica password for chatting:
'); + + input = $('') + div.append(input); + + button = $(''); + div.append(button); + + $("body").append(div); + } + button.click(function(){ - password = input.val(); + password = $("#jappixmini-password-query-input").val(); jappixmini_addon_set_client_secret(password); div.remove(); + + client_secret = getDB('jappix-mini', 'client-secret'); + callback(client_secret); }); - div.append(button); - $("body").append(div); } - - return client_secret; + else { + callback(client_secret); + } } -function jappixmini_addon_encrypt_password(password) { - client_secret = jappixmini_addon_get_client_secret(); +function jappixmini_addon_encrypt_password(password, callback) { + jappixmini_addon_get_client_secret(function(client_secret){ + // add \0 to password until it has the same length as secret + if (password.length>client_secret.length-1) throw "password too long"; + while (password.lengthclient_secret.length-1) throw "password too long"; - while (password.length Date: Sun, 15 Apr 2012 23:13:37 +0200 Subject: [PATCH 21/96] jappixmini: move jappixmini_manage_roster into callback --- jappixmini/jappixmini.php | 3 +-- jappixmini/lib.js | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/jappixmini/jappixmini.php b/jappixmini/jappixmini.php index 80fc237f..3b03743d 100644 --- a/jappixmini/jappixmini.php +++ b/jappixmini/jappixmini.php @@ -418,8 +418,7 @@ function jappixmini_script(&$a,&$s) { // add javascript to start Jappix Mini $a->page['htmlhead'] .= ""; diff --git a/jappixmini/lib.js b/jappixmini/lib.js index 94e465d1..351d03f9 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -130,7 +130,7 @@ function jappixmini_addon_subscribe() { sendSubscribe(xid, "subscribe"); } -function jappixmini_addon_start(server, username, bosh, encrypted, password, nickname) { +function jappixmini_addon_start(server, username, bosh, encrypted, password, nickname, contacts, autoapprove, autosubscribe) { handler = function(password){ // check if settings have changed, reinitialize jappix mini if this is the case settings_identifier = str_sha1(server); @@ -149,7 +149,9 @@ function jappixmini_addon_start(server, username, bosh, encrypted, password, nic // start jappix mini MINI_NICKNAME = nickname; + console.log("launchMini"); launchMini(true, false, server, username, password); + jappixmini_manage_roster(contacts, autoapprove, autosubscribe) } // decrypt password if necessary From bec530c9bb1c09113985a5dd7425bbc786115ddc Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Sun, 15 Apr 2012 23:19:37 +0200 Subject: [PATCH 22/96] jappixmini: use setPersistent to avoid password query window --- jappixmini/lib.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jappixmini/lib.js b/jappixmini/lib.js index 351d03f9..f62e5b8f 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -24,12 +24,12 @@ function jappixmini_addon_set_client_secret(password) { client_secret2 = str_sha1(salt2+password); client_secret = client_secret1 + client_secret2; - setDB('jappix-mini', 'client-secret', client_secret); + setPersistent('jappix-mini', 'client-secret', client_secret); console.log("client secret set"); } function jappixmini_addon_get_client_secret(callback) { - client_secret = getDB('jappix-mini', 'client-secret'); + client_secret = getPersistent('jappix-mini', 'client-secret'); if (client_secret===null) { div = document.getElementById("#jappixmini-password-query-div"); @@ -50,7 +50,7 @@ function jappixmini_addon_get_client_secret(callback) { jappixmini_addon_set_client_secret(password); div.remove(); - client_secret = getDB('jappix-mini', 'client-secret'); + client_secret = getPersistent('jappix-mini', 'client-secret'); callback(client_secret); }); } From e7d4a17428539920553efef56838dba974acb289 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Mon, 16 Apr 2012 01:04:45 +0200 Subject: [PATCH 23/96] jappixmini: improve roster management --- jappixmini/lib.js | 52 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/jappixmini/lib.js b/jappixmini/lib.js index f62e5b8f..d1858ff8 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -111,13 +111,53 @@ function jappixmini_manage_roster(contacts, autoapprove, autosubscribe) { }); // autosubscribe - if (autosubscribe) { - for (i=0; i Date: Mon, 16 Apr 2012 01:42:00 +0200 Subject: [PATCH 24/96] update jappixmini archive --- jappixmini.tgz | Bin 7397 -> 8018 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/jappixmini.tgz b/jappixmini.tgz index 6420352647672a99ba8968a766d44b3acd6a684b..82025d711e9a07a3fe71f36dee60c8af1d8aede9 100644 GIT binary patch literal 8018 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 Ui4!MIoa`=t1EGej^8la#0J*1!LjV8( literal 7397 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 From 360502d385b44a4d029e8a7947d5d29a4b7fec6d Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Mon, 16 Apr 2012 19:26:44 +0200 Subject: [PATCH 25/96] jappixmini: logging message when cron hook is called --- jappixmini/jappixmini.php | 1 + 1 file changed, 1 insertion(+) diff --git a/jappixmini/jappixmini.php b/jappixmini/jappixmini.php index 3b03743d..a648aba7 100644 --- a/jappixmini/jappixmini.php +++ b/jappixmini/jappixmini.php @@ -450,6 +450,7 @@ function jappixmini_cron(&$a, $d) { // go through list of users with jabber enabled $users = q("SELECT `uid` FROM `pconfig` WHERE `cat`='jappixmini' AND (`k`='autosubscribe' OR `k`='autoapprove') AND `v`='1'"); + logger("jappixmini: Update list of contacts' jabber accounts for ".count($users)." users."); foreach ($users as $row) { $uid = $row["uid"]; From af0366a9e22629a0050e8144884e6877bf103299 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Mon, 16 Apr 2012 19:28:23 +0200 Subject: [PATCH 26/96] jappixmini: add text field for configuration help --- jappixmini/jappixmini.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/jappixmini/jappixmini.php b/jappixmini/jappixmini.php index a648aba7..f476753a 100644 --- a/jappixmini/jappixmini.php +++ b/jappixmini/jappixmini.php @@ -69,6 +69,15 @@ register_hook('cron', 'addon/jappixmini/jappixmini.php', 'jappixmini_cron'); // Jappix source download as required by AGPL register_hook('about_hook', 'addon/jappixmini/jappixmini.php', 'jappixmini_download_source'); + +// set standard info text +$info_text = get_config("jappixmini", "infotext"); +if (!$info_text) set_config("jappixmini", "infotext", + "To get the chat working, you need to know a BOSH host which works with your Jabber account. ". + "An example of a BOSH server that works for all accounts is https://bind.jappix.com/, but keep ". + "in mind that the BOSH server can read along all chat messages. If you know that your Jabber ". + "server also provides an own BOSH server, it is much better to use this one!" +); } @@ -96,9 +105,21 @@ function jappixmini_plugin_admin(&$a, &$o) { else { $o .= '

Jappix is installed.

'; } + + // info text field + $o .= '
'; + $info_text = get_config("jappixmini", "infotext"); + $o .= '
'; + $o .= ''; } function jappixmini_plugin_admin_post(&$a) { + // set info text + $submit = $_REQUEST['jappixmini-admin-settings']; + if ($submit) { + $info_text = $_REQUEST['jappixmini-infotext']; + set_config("jappixmini", "infotext", $info_text); + } } function jappixmini_module() {} @@ -200,6 +221,10 @@ function jappixmini_settings(&$a, &$s) { $encrypt_checked = $encrypt ? ' checked="checked"' : ''; $encrypt_disabled = $encrypt ? '' : ' disabled="disabled"'; + $info_text = get_config("jappixmini", "infotext"); + $info_text = htmlentities($info_text); + $info_text = str_replace("\n", "
", $info_text); + if (!$activate) { // load scripts if not yet activated so that password can be saved $a->page['htmlhead'] .= ''."\r\n"; @@ -254,6 +279,7 @@ function jappixmini_settings(&$a, &$s) { $s .= ''; $s .= ' '; $s .= '
'; + if ($info_text) $s .= '
Configuration help:

'.$info_text.'

'; $s .= ''; $s .= ' '; $s .= ''; From a53167da1a68b977ba5a67342dfa17fd7e8a2974 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Mon, 16 Apr 2012 21:04:45 +0200 Subject: [PATCH 27/96] jappixmini: warn if cron job does not get executed --- jappixmini/jappixmini.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/jappixmini/jappixmini.php b/jappixmini/jappixmini.php index f476753a..88233063 100644 --- a/jappixmini/jappixmini.php +++ b/jappixmini/jappixmini.php @@ -106,6 +106,10 @@ function jappixmini_plugin_admin(&$a, &$o) { $o .= '

Jappix is installed.

'; } + // warn if cron job has not yet been executed + $cron_run = get_config("jappixmini", "last_cron_execution"); + if (!$cron_run) $o .= "

Warning: The cron job has not yet been executed. If this message is still there after some time (usually 10 minutes), this means that autosubscribe and autoaccept will not work.

"; + // info text field $o .= '
'; $info_text = get_config("jappixmini", "infotext"); @@ -472,6 +476,8 @@ function jappixmini_login(&$a, &$o) { function jappixmini_cron(&$a, $d) { // For autosubscribe/autoapprove, we need to maintain a list of jabber addresses of our contacts. + set_config("jappixmini", "last_cron_execution", $d); + if (!file_exists("addon/jappixmini/jappix")) return; // go through list of users with jabber enabled From b4aa673c5d4f1ffce367e2d317b12384bf5ba0c7 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Mon, 16 Apr 2012 21:16:24 +0200 Subject: [PATCH 28/96] jappixmini: make BOSH proxy optional --- jappixmini/README | 5 +++-- jappixmini/jappixmini.php | 43 ++++++++++++++++++++++++--------------- jappixmini/lib.js | 13 +++++++----- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/jappixmini/README b/jappixmini/README index 822f4391..222b85a5 100644 --- a/jappixmini/README +++ b/jappixmini/README @@ -10,8 +10,9 @@ the Jappix project (https://bind.jappix.com/) is not locked to a specific XMPP provider, but keep in mind that only personal usage is approved according to http://codingteam.net/project/jappix/doc/BoshServer. If you have a larger server, it is recommended that you install your own BOSH -server, add it to the tag in jappix/store/conf/hosts.xml, and disable -the bosh proxy in jappix/store/conf/main.xml. +server and recommend it using the configuration help field. If it is on the +same server, you can also deactivate the BOSH proxy. This should improve the +performance. The addon has an experimental autosubscribe and autosuggest functionality which tries to add your Friendica contacts to your roster automatically. diff --git a/jappixmini/jappixmini.php b/jappixmini/jappixmini.php index 88233063..b177507a 100644 --- a/jappixmini/jappixmini.php +++ b/jappixmini/jappixmini.php @@ -70,7 +70,7 @@ register_hook('cron', 'addon/jappixmini/jappixmini.php', 'jappixmini_cron'); // Jappix source download as required by AGPL register_hook('about_hook', 'addon/jappixmini/jappixmini.php', 'jappixmini_download_source'); -// set standard info text +// set standard configuration $info_text = get_config("jappixmini", "infotext"); if (!$info_text) set_config("jappixmini", "infotext", "To get the chat working, you need to know a BOSH host which works with your Jabber account. ". @@ -78,6 +78,9 @@ if (!$info_text) set_config("jappixmini", "infotext", "in mind that the BOSH server can read along all chat messages. If you know that your Jabber ". "server also provides an own BOSH server, it is much better to use this one!" ); + +$bosh_proxy = get_config("jappixmini", "bosh_proxy"); +if ($bosh_proxy==="") set_config("jappixmini", "bosh_proxy", 1); } @@ -110,10 +113,18 @@ function jappixmini_plugin_admin(&$a, &$o) { $cron_run = get_config("jappixmini", "last_cron_execution"); if (!$cron_run) $o .= "

Warning: The cron job has not yet been executed. If this message is still there after some time (usually 10 minutes), this means that autosubscribe and autoaccept will not work.

"; + // bosh proxy + $bosh_proxy = intval(get_config("jappixmini", "bosh_proxy")); + $bosh_proxy = intval($bosh_proxy) ? ' checked="checked"' : ''; + $o .= ''; + $o .= '
'; + // info text field - $o .= '
'; $info_text = get_config("jappixmini", "infotext"); - $o .= '
'; + $o .= '


'; + $o .= '

'; + + // submit button $o .= ''; } @@ -122,7 +133,9 @@ function jappixmini_plugin_admin_post(&$a) { $submit = $_REQUEST['jappixmini-admin-settings']; if ($submit) { $info_text = $_REQUEST['jappixmini-infotext']; + $bosh_proxy = intval($_REQUEST['jappixmini-proxy']); set_config("jappixmini", "infotext", $info_text); + set_config("jappixmini", "bosh_proxy", $bosh_proxy); } } @@ -255,13 +268,9 @@ function jappixmini_settings(&$a, &$s) { $s .= ' '; $s .= '
'; - $conf = file_get_contents("addon/jappixmini/jappix/store/conf/main.xml"); - preg_match("/(.*)<\/bosh_proxy>/", $conf, $matches); - if ($matches[1]=="on") { - $s .= ''; - $s .= ' '; - $s .= '
'; - } + $s .= ''; + $s .= ' '; + $s .= '
'; $s .= ''; $s .= ' '; @@ -410,11 +419,13 @@ function jappixmini_script(&$a,&$s) { $autosubscribe = get_pconfig(local_user(),'jappixmini','autosubscribe'); $autosubscribe = intval($autosubscribe); - // deactivate bosh host if proxy is off - $conf = file_get_contents("addon/jappixmini/jappix/store/conf/main.xml"); - preg_match("/(.*)<\/bosh_proxy>/", $conf, $matches); - if ($matches[1]!="on") { - $bosh = ''; + // set proxy if necessary + $use_proxy = get_config('jappixmini','bosh_proxy'); + if ($use_proxy) { + $proxy = $a->get_baseurl().'/addon/jappixmini/jappix/php/bosh.php'; + } + else { + $proxy = ""; } // get a list of jabber accounts of the contacts @@ -448,7 +459,7 @@ function jappixmini_script(&$a,&$s) { // add javascript to start Jappix Mini $a->page['htmlhead'] .= ""; diff --git a/jappixmini/lib.js b/jappixmini/lib.js index d1858ff8..8f4a2669 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -170,7 +170,7 @@ function jappixmini_addon_subscribe() { sendSubscribe(xid, "subscribe"); } -function jappixmini_addon_start(server, username, bosh, encrypted, password, nickname, contacts, autoapprove, autosubscribe) { +function jappixmini_addon_start(server, username, proxy, bosh, encrypted, password, nickname, contacts, autoapprove, autosubscribe) { handler = function(password){ // check if settings have changed, reinitialize jappix mini if this is the case settings_identifier = str_sha1(server); @@ -183,15 +183,18 @@ function jappixmini_addon_start(server, username, bosh, encrypted, password, nic if (saved_identifier != settings_identifier) removeDB('jappix-mini', 'dom'); setDB("jappix-mini", "settings_identifier", settings_identifier); - // set bosh host - if (bosh) - HOST_BOSH = HOST_BOSH+"?host_bosh="+encodeURI(bosh); + // set HOST_BOSH + if (proxy) + HOST_BOSH = proxy+"?host_bosh="+encodeURI(bosh); + else + HOST_BOSH = bosh; // start jappix mini MINI_NICKNAME = nickname; + LOCK_HOST = "off"; console.log("launchMini"); launchMini(true, false, server, username, password); - jappixmini_manage_roster(contacts, autoapprove, autosubscribe) + jappixmini_manage_roster(contacts, autoapprove, autosubscribe) } // decrypt password if necessary From 479d8ad96fc3f9398f80a6b2418a400afce5d767 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Mon, 16 Apr 2012 21:40:23 +0200 Subject: [PATCH 29/96] jappixmini: move modified BOSH proxy out of Jappix source tree to be able to use vanilla Jappix. This is possible because bosh.php is under MIT license. --- jappixmini/README | 11 +-- jappixmini/jappixmini.php | 19 +++- jappixmini/proxy.php | 178 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 7 deletions(-) create mode 100644 jappixmini/proxy.php diff --git a/jappixmini/README b/jappixmini/README index 222b85a5..b833dae4 100644 --- a/jappixmini/README +++ b/jappixmini/README @@ -27,8 +27,9 @@ Installation Jappix Mini (AGPL license) is not distributed with this addon. You need to install it manually: -* Download latest zip file named friendica-addon-* from - https://github.com/Leberwurscht/jappix/tags and place the zip file in the - addon folder. Make sure to name it 'jappix.zip' - the download link required - by the AGPL points there. -* Unpack the zip file, rename the folder to 'jappix'. Do not delete jappix.zip. +* Download latest jappix version from https://github.com/jappix/jappix/tags to + addon/jappixmini/jappix.zip. Really make sure you name it 'jappix.zip' - the + download link required by the AGPL points there. +* Unpack the zip file to addon/jappixmini/jappix/. Do not delete jappix.zip. +* Delete the file addon/jappixmini/jappix/index.php. This is important, because + otherwise the installer of Jappix would be publicly accessible. diff --git a/jappixmini/jappixmini.php b/jappixmini/jappixmini.php index b177507a..93689a1e 100644 --- a/jappixmini/jappixmini.php +++ b/jappixmini/jappixmini.php @@ -100,7 +100,22 @@ function jappixmini_plugin_admin(&$a, &$o) { // display instructions and warnings on addon settings page for admin if (!file_exists("addon/jappixmini/jappix")) { - $o .= '

You need to install the Jappix application, adapted for Friendica (see README).

'; + $o .= '

You need to install the Jappix application (see README).

'; + } + else if (file_exists("addon/jappixmini/jappix/index.php")) { + // try to delete automatically + try { + unlink("addon/jappixmini/jappix/index.php"); + } + catch (Exception $e) {} + + // warn admin if this is not possible + if (file_exists("addon/jappixmini/jappix/index.php")) + $o .= '

You must delete addon/jappixmini/jappix/index.php (see README).

'; + else { + info("Deleted addon/jappixmini/jappix/index.php automatically."); + $o .= '

Jappix is installed.

'; + } } else if (!file_exists("addon/jappixmini/jappix.zip")) { $o .= '

The source archive jappix.zip does not exist. This is probably a violation of the Jappix License (see README).

'; @@ -422,7 +437,7 @@ function jappixmini_script(&$a,&$s) { // set proxy if necessary $use_proxy = get_config('jappixmini','bosh_proxy'); if ($use_proxy) { - $proxy = $a->get_baseurl().'/addon/jappixmini/jappix/php/bosh.php'; + $proxy = $a->get_baseurl().'/addon/jappixmini/proxy.php'; } else { $proxy = ""; diff --git a/jappixmini/proxy.php b/jappixmini/proxy.php new file mode 100644 index 00000000..72de341c --- /dev/null +++ b/jappixmini/proxy.php @@ -0,0 +1,178 @@ + array( + 'method' => 'POST', + 'content' => $data + ) + ); + + $parameters['http']['header'] = $headers; + + // Change default timeout + ini_set('default_socket_timeout', 30); + + // Create the connection + $stream = @stream_context_create($parameters); + $connection = @fopen($HOST_BOSH, 'rb', false, $stream); + + // Failed to connect! + if($connection == false) { + header('Status: 502 Proxy Error', true, 502); + exit('HTTP/1.1 502 Proxy Error'); + } + + // Allow stream blocking to handle incoming BOSH data + @stream_set_blocking($connection, true); + + // Get the output content + $output = @stream_get_contents($connection); +} + +// Cache headers +header('Cache-Control: no-cache, must-revalidate'); +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); + +// POST output +if($method == 'POST') { + // XML header + header('Content-Type: text/xml; charset=utf-8'); + + if(!$output) + echo(''); + else + echo($output); +} + +// GET output +if($method == 'GET') { + // JSON header + header('Content-type: application/json'); + + // Encode output to JSON + $json_output = json_encode($output); + + if(($output == false) || ($output == '') || ($json_output == 'null')) + echo($callback.'({"reply":""});'); + else + echo($callback.'({"reply":'.$json_output.'});'); +} + +// Close the connection +if($use_curl) + curl_close($connection); +else + @fclose($connection); + +?> From a656c73bf5f444804ceb55c939929729e60ad5d1 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Mon, 16 Apr 2012 23:38:00 +0200 Subject: [PATCH 30/96] jappixmini: increase client priority --- jappixmini/lib.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/jappixmini/lib.js b/jappixmini/lib.js index 8f4a2669..d02f5d5c 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -194,6 +194,11 @@ function jappixmini_addon_start(server, username, proxy, bosh, encrypted, passwo LOCK_HOST = "off"; console.log("launchMini"); launchMini(true, false, server, username, password); + + // increase priority over other Jabber clients + priority = 101; + sendPresence(null,null,priority); + jappixmini_manage_roster(contacts, autoapprove, autosubscribe) } From 80a0ccb6c80746bd929b5d2b377c995bb2423606 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Mon, 16 Apr 2012 23:38:21 +0200 Subject: [PATCH 31/96] jappixmini: improve README --- jappixmini/README | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jappixmini/README b/jappixmini/README index b833dae4..95dd7b02 100644 --- a/jappixmini/README +++ b/jappixmini/README @@ -32,4 +32,5 @@ install it manually: download link required by the AGPL points there. * Unpack the zip file to addon/jappixmini/jappix/. Do not delete jappix.zip. * Delete the file addon/jappixmini/jappix/index.php. This is important, because - otherwise the installer of Jappix would be publicly accessible. + otherwise the installer of Jappix would be publicly accessible. If you are + lucky, the file is deleted automatically by the addon. From 74799ed141ba4d2da96c1bb20f463a365ba6a72a Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Mon, 16 Apr 2012 23:39:21 +0200 Subject: [PATCH 32/96] jappixmini: throw error when decrypt_password fails --- jappixmini/lib.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jappixmini/lib.js b/jappixmini/lib.js index d02f5d5c..8e698654 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -84,7 +84,7 @@ function jappixmini_addon_decrypt_password(encrypted_password, callback) { // remove \0 first_null = password.indexOf("\0") - // TODO: check first_null==null + if (first_null==-1) throw "Decrypted password does not contain \\0"; password = password.substr(0, first_null); callback(password); From 1da347bd864f4ead028e7b5e36d1f224277c2786 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Mon, 16 Apr 2012 23:39:46 +0200 Subject: [PATCH 33/96] jappixmini: reinitialize jappix if proxy changed --- jappixmini/lib.js | 1 + 1 file changed, 1 insertion(+) diff --git a/jappixmini/lib.js b/jappixmini/lib.js index 8e698654..43a6470b 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -175,6 +175,7 @@ function jappixmini_addon_start(server, username, proxy, bosh, encrypted, passwo // check if settings have changed, reinitialize jappix mini if this is the case settings_identifier = str_sha1(server); settings_identifier += str_sha1(username); + settings_identifier += str_sha1(proxy); settings_identifier += str_sha1(bosh); settings_identifier += str_sha1(password); settings_identifier += str_sha1(nickname); From 35090ac2a7392a5f807bdb59b1b11a713f0928e8 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Mon, 16 Apr 2012 23:40:40 +0200 Subject: [PATCH 34/96] jappixmini: do not ask user but wait if a subscriber claims to be from Friendica --- jappixmini/lib.js | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/jappixmini/lib.js b/jappixmini/lib.js index 43a6470b..f11fada6 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -99,10 +99,28 @@ function jappixmini_manage_roster(contacts, autoapprove, autosubscribe) { var from = fullXID(getStanzaFrom(presence)); var xid = bareXID(from); + var pstatus = presence.getStatus(); - approve = true; - if ((!autoapprove) || contacts[xid]===undefined) - approve = confirm("Accept "+xid+" for chat?"); + if (autoapprove && contacts[xid]!==undefined) { + // approve known address + approve = true; + console.log("Approve known Friendica contact "+xid+"."); + } + else if (autoapprove && pstatus && pstatus.indexOf("Friendica")!=-1) { + // Unknown address claims to be a Friendica contact. + // This is probably because the other side knows our + // address, but we do not know the other side yet. + // But it's only a matter of time, so wait - do not + // approve yet and do not annoy the user by asking. + approve = false; + console.log("Do not approve unknown Friendica contact "+xid+" - wait instead."); + } + else { + // In all other cases, ask the user. + message = "Accept "+xid+" for chat?"; + if (pstatus) message += "\n\nStatus:\n"+pstatus; + approve = confirm(message); + } if (approve) { acceptSubscribe(xid, contacts[xid]); @@ -144,6 +162,11 @@ function jappixmini_manage_roster(contacts, autoapprove, autosubscribe) { var presence = new JSJaCPresence(); presence.setTo(xid); presence.setType("subscribe"); + + // must contain the word "~Friendica" so the other side knows + // how to handle this + presence.setStatus("I'm "+MINI_NICKNAME+" from ~Friendica.\n[machine-generated message]"); + con.send(presence); console.log("subscribed to "+xid); From 20f1d45d9b7aa57f8e1ad27ed673a03884e8eebb Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Tue, 17 Apr 2012 00:01:30 +0200 Subject: [PATCH 35/96] jappixmini: combine included jappix scripts --- jappixmini/jappixmini.php | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/jappixmini/jappixmini.php b/jappixmini/jappixmini.php index 93689a1e..6840c1d9 100644 --- a/jappixmini/jappixmini.php +++ b/jappixmini/jappixmini.php @@ -260,11 +260,7 @@ function jappixmini_settings(&$a, &$s) { if (!$activate) { // load scripts if not yet activated so that password can be saved $a->page['htmlhead'] .= ''."\r\n"; - $a->page['htmlhead'] .= ''."\r\n"; - - $a->page['htmlhead'] .= ''."\r\n"; - $a->page['htmlhead'] .= ''."\r\n"; - $a->page['htmlhead'] .= ''."\r\n"; + $a->page['htmlhead'] .= ''."\r\n"; $a->page['htmlhead'] .= ''."\r\n"; } @@ -410,11 +406,7 @@ function jappixmini_script(&$a,&$s) { if (!$activate) return; $a->page['htmlhead'] .= ''."\r\n"; - $a->page['htmlhead'] .= ''."\r\n"; - - $a->page['htmlhead'] .= ''."\r\n"; - $a->page['htmlhead'] .= ''."\r\n"; - $a->page['htmlhead'] .= ''."\r\n"; + $a->page['htmlhead'] .= ''."\r\n"; $a->page['htmlhead'] .= ''."\r\n"; @@ -486,11 +478,8 @@ function jappixmini_login(&$a, &$o) { if (!file_exists("addon/jappixmini/jappix")) return; - // for setDB, needed by jappixmini_addon_set_client_secret - $a->page['htmlhead'] .= ''."\r\n"; - - // for str_sha1, needed by jappixmini_addon_set_client_secret - $a->page['htmlhead'] .= ''."\r\n"; + // for setDB and str_sha1, needed by jappixmini_addon_set_client_secret + $a->page['htmlhead'] .= ''."\r\n"; // for jappixmini_addon_set_client_secret $a->page['htmlhead'] .= ''."\r\n"; From 7ea17b33c8f7ddce61d2e06bea2d14e99826948c Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Tue, 17 Apr 2012 00:04:34 +0200 Subject: [PATCH 36/96] jappixmini: reduce unnecessary logging --- jappixmini/lib.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/jappixmini/lib.js b/jappixmini/lib.js index f11fada6..dd2d7955 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -143,7 +143,6 @@ function jappixmini_manage_roster(contacts, autoapprove, autosubscribe) { xid = $(this).attr("jid"); name = $(this).attr("name"); subscription = $(this).attr("subscription"); - console.log(xid+" "+subscription); // ignore accounts not in the list if (contacts[xid]===undefined) return; @@ -178,7 +177,7 @@ function jappixmini_manage_roster(contacts, autoapprove, autosubscribe) { item.setAttribute('name', contacts[xid]); item.appendChild(iq.buildNode('group', {'xmlns': NS_ROSTER}, "Friendica")); con.send(iq); - console.log("added to roster "+xid); + console.log("added to roster: "+xid); } }); } @@ -216,7 +215,6 @@ function jappixmini_addon_start(server, username, proxy, bosh, encrypted, passwo // start jappix mini MINI_NICKNAME = nickname; LOCK_HOST = "off"; - console.log("launchMini"); launchMini(true, false, server, username, password); // increase priority over other Jabber clients From 0164bdf11e1dd8ca2b89955a1c390057858e3f8e Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Tue, 17 Apr 2012 00:41:22 +0200 Subject: [PATCH 37/96] jappixmini: add to Friendica group and authorize --- jappixmini/lib.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/jappixmini/lib.js b/jappixmini/lib.js index dd2d7955..bbbc7e34 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -147,9 +147,24 @@ function jappixmini_manage_roster(contacts, autoapprove, autosubscribe) { // ignore accounts not in the list if (contacts[xid]===undefined) return; - // TODO: add to Friendica group + // add to Friendica group if necessary + groups = []; + $(this).find('group').each(function() { + var group_text = $(this).text(); + if(group_text) groups.push(group_text); + }); - // TODO: unblock and authorize if necessary + if ($.inArray("Friendica", groups)==-1) { + console.log("Add "+xid+" to Friendica group."); + groups.push("Friendica"); + sendRoster(xid, null, null, groups); + console.log("Added "+xid+" to Friendica group."); + } + + // authorize if necessary + if (subscription=="to") { + sendSubscribe(xid, 'subscribed'); + } // remove from list delete contacts[xid]; From 54d98689fab14505149d4dab770ba034990a0920 Mon Sep 17 00:00:00 2001 From: friendica Date: Mon, 16 Apr 2012 22:57:34 -0700 Subject: [PATCH 38/96] small fix to mathjax. --- convpath.tgz | Bin 0 -> 814 bytes facebook.tgz | Bin 17977 -> 18227 bytes fromgplus/fromgplus.php | 181 -------------------------------------- fromgplus/tofriendica.php | 128 --------------------------- mathjax.tgz | Bin 0 -> 2169 bytes mathjax/mathjax.php | 4 +- 6 files changed, 3 insertions(+), 310 deletions(-) create mode 100644 convpath.tgz delete mode 100755 fromgplus/fromgplus.php delete mode 100644 fromgplus/tofriendica.php create mode 100644 mathjax.tgz diff --git a/convpath.tgz b/convpath.tgz new file mode 100644 index 0000000000000000000000000000000000000000..2c43d3d380e73b78e1021da199148676ab1a91ba GIT binary patch literal 814 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 literal 17977 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 - * - */ - -function fromgplus_install() { - register_hook('plugin_settings', 'addon/fromgplus/fromgplus.php', 'fromgplus_addon_settings'); - register_hook('plugin_settings_post', 'addon/fromgplus/fromgplus.php', 'fromgplus_addon_settings_post'); -} - -function fromgplus_uninstall() { - unregister_hook('plugin_settings', 'addon/fromgplus/fromgplus.php', 'fromgplus_addon_settings'); - unregister_hook('plugin_settings_post', 'addon/fromgplus/fromgplus.php', 'fromgplus_addon_settings_post'); -} - -function fromgplus_addon_settings(&$a,&$s) { - - if(! local_user()) - return; - - $enable_checked = (intval(get_pconfig(local_user(),'fromgplus','enable')) ? ' checked="checked"' : ''); - $account = get_pconfig(local_user(),'fromgplus','account'); - - $s .= '
'; - $s .= '

' . t('Google+ Import Settings').'

'; - $s .= '
'; - - $s .= ''; - $s .= ''; - $s .= '
'; - $s .= ''; - $s .= ''; - $s .= '
'; - - $s .= '
'; - $s .= '
'; - - return; -} - -function fromgplus_addon_settings_post(&$a,&$b) { - - if(! local_user()) - return; - - if($_POST['fromgplus-submit']) { - set_pconfig(local_user(),'fromgplus','account',trim($_POST['fromgplus-account'])); - $enable = ((x($_POST,'fromgplus-enable')) ? intval($_POST['fromgplus-enable']) : 0); - set_pconfig(local_user(),'fromgplus','enable', $enable); - info( t('Google+ Import Settings saved.') . EOL); - } -} -/* -function html2bbcode($html) { - - $bbcode = html_entity_decode($html, ENT_QUOTES, 'UTF-8'); - - $bbcode = str_replace(array("\n"), array(""), $bbcode); - $bbcode = str_replace(array("", ""), array("[b]", "[/b]"), $bbcode); - $bbcode = str_replace(array("", ""), array("[i]", "[/i]"), $bbcode); - $bbcode = str_replace(array("", ""), array("[s]", "[/s]"), $bbcode); - $bbcode = str_replace(array("
"), array("\n"), $bbcode); - - $bbcode = trim(strip_tags($bbcode)); - return($bbcode); -} - -function friendicapost($post) { - global $friendica; - - $api = new Statusnet($friendica["user"], $friendica["pw"], "GooglePlus", $friendica["server"]); - $ret = $api->updateStatus($post); - $api->endSession(); -} - -function handleattachments($item) { - $post = ""; - - foreach ($item->object->attachments as $attachment) { - switch($attachment->objectType) { - case "video": - //$post .= "\n\n[url=".$attachment->url."]". - // "[size=large][b]".html2bbcode($attachment->displayName)."[/b][/size][/url]\n"; - $post .= "\n\n[bookmark=".$attachment->url."]".html2bbcode($attachment->displayName)."[/bookmark]\n"; - - //if (strpos($attachment->embed->url, "youtube.com")) - // $post .= "[youtube]".$attachment->url."[/youtube]\n"; - //else - /// $post .= "[url=".$attachment->url."][img]".$attachment->image->url."[/img][/url]\n"; - - ///$post .= "[quote]".trim(html2bbcode($attachment->content))."[/quote]"; - break; - - case "article": - //$post .= "\n\n[url=".$attachment->url."]". - // "[size=large][b]".html2bbcode($attachment->displayName)."[/b][/size][/url]\n"; - $post .= "\n\n[bookmark=".$attachment->url."]".html2bbcode($attachment->displayName)."[/bookmark]\n"; - $post .= "[quote]".trim(html2bbcode($attachment->content))."[/quote]"; - break; - - case "photo": - //$post .= "\n\n[url=".$attachment->fullImage->url."]". - // "[img]".$attachment->fullImage->url."[/img][/url]\n"; - $post .= "\n\n[img]".$attachment->fullImage->url."[/img]\n"; - if ($attachment->displayName != "") - $post .= html2bbcode($attachment->displayName)."\n"; - break; - - case "photo-album": - $post .= "\n\n[url=".$attachment->url."]". - "[size=large][b]".html2bbcode($attachment->displayName)."[/b][/size][/url]\n"; - break; - - default: - print_r($attachment); - die(); - break; - } - } - return($post); -} - -$result = file_get_contents("https://www.googleapis.com/plus/v1/people/".$google["id"]."/activities/public?alt=json&pp=1&key=".$google["key"]."&maxResults=".$google["maxfetch"]); -$activities = json_decode($result); - -$state = array("lastid"=>''); -if (file_exists($statefile)) - $state = unserialize(file_get_contents($statefile)); - -$lastid = ""; - -foreach($activities->items as $item) { - if ($item->id == $state["lastid"]) - break; - - if ($lastid == "") - $lastid = $item->id; - - switch($item->object->objectType) { - case "note": - $post = html2bbcode($item->object->content); - - if (is_array($item->object->attachments)) - $post .= handleattachments($item); - friendicapost($post); - break; - - case "activity": - $post = html2bbcode($item->annotation)."\n"; - //$post .= html2bbcode("♲ "); - $post .= html2bbcode("♻ "); - $post .= "[url=".$item->object->actor->url."]".$item->object->actor->displayName."[/url]"; - $post .= " \n"; - //$post .= "[quote]"; - - $post .= html2bbcode($item->object->content); - - if (is_array($item->object->attachments)) - $post .= "\n".trim(handleattachments($item)); - - //$post .= "[/quote]"; - - friendicapost($post); - break; - - default: - print_r($item); - die(); - break; - } -} - -if ($lastid != "") { - $state['lastid'] = $lastid; - file_put_contents($statefile, serialize($state)); -} -*/ diff --git a/fromgplus/tofriendica.php b/fromgplus/tofriendica.php deleted file mode 100644 index b185ecc1..00000000 --- a/fromgplus/tofriendica.php +++ /dev/null @@ -1,128 +0,0 @@ -", "
"), array("[b]", "[/b]"), $bbcode); - $bbcode = str_replace(array("", ""), array("[i]", "[/i]"), $bbcode); - $bbcode = str_replace(array("", ""), array("[s]", "[/s]"), $bbcode); - $bbcode = str_replace(array("
"), array("\n"), $bbcode); - - $bbcode = trim(strip_tags($bbcode)); - return($bbcode); -} - -function friendicapost($post) { - global $friendica; - - $api = new Statusnet($friendica["user"], $friendica["pw"], "GooglePlus", $friendica["server"]); - $ret = $api->updateStatus($post); - $api->endSession(); -} - -function handleattachments($item) { - $post = ""; - - foreach ($item->object->attachments as $attachment) { - switch($attachment->objectType) { - case "video": - //$post .= "\n\n[url=".$attachment->url."]". - // "[size=large][b]".html2bbcode($attachment->displayName)."[/b][/size][/url]\n"; - $post .= "\n\n[bookmark=".$attachment->url."]".html2bbcode($attachment->displayName)."[/bookmark]\n"; - - //if (strpos($attachment->embed->url, "youtube.com")) - // $post .= "[youtube]".$attachment->url."[/youtube]\n"; - //else - /// $post .= "[url=".$attachment->url."][img]".$attachment->image->url."[/img][/url]\n"; - - ///$post .= "[quote]".trim(html2bbcode($attachment->content))."[/quote]"; - break; - - case "article": - //$post .= "\n\n[url=".$attachment->url."]". - // "[size=large][b]".html2bbcode($attachment->displayName)."[/b][/size][/url]\n"; - $post .= "\n\n[bookmark=".$attachment->url."]".html2bbcode($attachment->displayName)."[/bookmark]\n"; - $post .= "[quote]".trim(html2bbcode($attachment->content))."[/quote]"; - break; - - case "photo": - //$post .= "\n\n[url=".$attachment->fullImage->url."]". - // "[img]".$attachment->fullImage->url."[/img][/url]\n"; - $post .= "\n\n[img]".$attachment->fullImage->url."[/img]\n"; - if ($attachment->displayName != "") - $post .= html2bbcode($attachment->displayName)."\n"; - break; - - case "photo-album": - $post .= "\n\n[url=".$attachment->url."]". - "[size=large][b]".html2bbcode($attachment->displayName)."[/b][/size][/url]\n"; - break; - - default: - print_r($attachment); - die(); - break; - } - } - return($post); -} - -$result = file_get_contents("https://www.googleapis.com/plus/v1/people/".$google["id"]."/activities/public?alt=json&pp=1&key=".$google["key"]."&maxResults=".$google["maxfetch"]); -$activities = json_decode($result); - -$state = array("lastid"=>''); -if (file_exists($statefile)) - $state = unserialize(file_get_contents($statefile)); - -$lastid = ""; - -foreach($activities->items as $item) { - if ($item->id == $state["lastid"]) - break; - - if ($lastid == "") - $lastid = $item->id; - - switch($item->object->objectType) { - case "note": - $post = html2bbcode($item->object->content); - - if (is_array($item->object->attachments)) - $post .= handleattachments($item); - friendicapost($post); - break; - - case "activity": - $post = html2bbcode($item->annotation)."\n"; - //$post .= html2bbcode("♲ "); - $post .= html2bbcode("♻ "); - $post .= "[url=".$item->object->actor->url."]".$item->object->actor->displayName."[/url]"; - $post .= " \n"; - //$post .= "[quote]"; - - $post .= html2bbcode($item->object->content); - - if (is_array($item->object->attachments)) - $post .= "\n".trim(handleattachments($item)); - - //$post .= "[/quote]"; - - friendicapost($post); - break; - - default: - print_r($item); - die(); - break; - } -} - -if ($lastid != "") { - $state['lastid'] = $lastid; - file_put_contents($statefile, serialize($state)); -} -?> diff --git a/mathjax.tgz b/mathjax.tgz new file mode 100644 index 0000000000000000000000000000000000000000..bb591e1d30794854f624f4c3649685ddebb711ea GIT binary patch literal 2169 zcmV-<2!{6`iwFQQ1&vPt1MOICZyPrf_SfvMm=kneDUeo{EZM0WJ58G$u0`AywR^Zj zUA`Tk(oAML^DA(ir@(1t01y#C0vCa2pKt|x8>^4 zM=!Fzr~S=VscA72*;sR9L^jhOExFpz{sulm?l_m){9)m4SE+a^XEUx^Vd*BGz#Bcb z0d{B6y1Tc%it)PV*CKlu&%Q19Q~CBolK-N2w|H%14YZo915NtV`%p}w^*0Pj%+fJt zw`#T9cHU)zDOBcs((3_g6dH)*ZbpU*Ek-lVSE7rsLYJZqfCGMkE7tFCgjKkS4!6Lv&qmMkr$zpflc-!9wl zcNDa~A}4*-)PK}PO+tqgn0=&@?e#9huMO^OwdhA1oNc=6`ohQ8HG=K`1RiU{M0VHn6I(eCMBAj z;a$bGqDUEsl@iAA3<{0H0at(naEINP7fQ(j1tc)Jv7ZZF;QnS4kO^1@ z{P&D0#cse_fplOOQO4Tf$SK^&Lct1gpo59FlIjW!o8Z<2awV3`aMVnsk+UnmZ&u1C zJOzP{^?|oD4YFW8hhbU%g+Ysmz6p0IfXq~Pt#lwp>)&Xyr^lJT>7|k-(Ho5m zWMrxREs&^@Ma!YY#G?cPBq0UKTna4=rn7WEX_JRQR>P4F5MWgGQ7J>>7PS)iWvwKw z))mr5C2pv+L>mV-(5eBmZwHSamD|FyD2kIT^6VnjtYVxL?~yjW zQaN`g@2V{+ya!PbF9J>7i3ohmgzlb+Zl7 zD#o0NNX;muw00kSrrZHf(KR)xe+qQHwhnJ^p_=?;idtoz%G=ncZcSPEO0|wT=`F z&tl)eU(p45T&y9C;HBb5QT-u7!2HmjRpL*ft?d#fTvV&@*5Eh^VQE`slo;_*8d zgmN3i`u&TaTnS(S5XxGg+3Y|d^%5}mI~$R4<=p?e^W6;^KHYzP`TWJ%%ZEAR1MfeB z(SF~){|pXC!~P+?{~R3b4|nfBpJV)mH=W>OjwO=oFj8)`EAw69LkQxwP?3jpytVhx z!3t5uH4H-8mO)3=nTB0)4KY(3*hEsfi`X+kP=*CJbD4N#3noggG+~oOX3>bYu1zTq zDfuxJbZg9T_zEsg`f;$CNA0WeEN z#u0Jng$61|eG{el?}bqGR8j$Nkcx?7=w}U6B^MjA;#3w%hwzk6DT~f`VruXU_7J#E zbElCb;aX?!nlMMoIm@{9IXKcNl#_@4)?$)!wDbqtq?PXlC||*;Zs(m#`PZP@(oSA!|QfXV^N}(7n%x8 z3iJ-c%4Er1O6nkJvhJx<@O7wfwBd{J^0j-X2``bEUq?~Y@v=IAzb-MUCEB0z*rl7s zl#aP?3o4GRap^jwq!{`PvtjdS&(N2(nvd#vQxY^9VQw0FCYN||)(-@Y`USb*YC*36!8u2Gjj?B@q%Etj)oQg-eI;_0(IxU_SQpE)t|19s zIqhmEp%(=H@p#MY@w(Lbv#-c&37p;~j=@bwH9_Cb7FI^^rI#_D|Eu!*0nYdU|L+g_ zqkH^692|^x{Qo(|?Jc1iq6trvv3*sdBmm-NXxiD6s@N%%=*o&=@'; } else { From 314ee186258dba100bc75492c65aaf00eb1b0730 Mon Sep 17 00:00:00 2001 From: friendica Date: Tue, 17 Apr 2012 00:05:34 -0700 Subject: [PATCH 39/96] extra arg to facebook_plugin_admin_post() --- facebook.tgz | Bin 18227 -> 18227 bytes facebook/facebook.php | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/facebook.tgz b/facebook.tgz index 6660aa8d369913595861cc74defeed24f2efe65b..e3709ec48955c9965509593e6f21238148693d4e 100644 GIT binary patch delta 18142 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 J4@v+S0RV{J&;bAd delta 18121 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

s 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 Date: Tue, 17 Apr 2012 09:01:49 +0000 Subject: [PATCH 40/96] PHPDoc + some Notices --- facebook/facebook.php | 114 +++++++++++++++++++----------------------- 1 file changed, 52 insertions(+), 62 deletions(-) diff --git a/facebook/facebook.php b/facebook/facebook.php index d681c4dc..dff43251 100644 --- a/facebook/facebook.php +++ b/facebook/facebook.php @@ -72,11 +72,10 @@ function facebook_module() {} // If $_REQUEST["realtime_cb"] is set, this is a callback from the Real-Time Updates API /** - * @param $a - * @return mixed + * @param App $a */ function facebook_init(&$a) { - + if (x($_REQUEST, "realtime_cb") && x($_REQUEST, "realtime_cb")) { logger("facebook_init: Facebook Real-Time callback called", LOGGER_DEBUG); @@ -170,7 +169,7 @@ function facebook_init(&$a) { $r = q("SELECT `uid` FROM `user` WHERE `nickname` = '%s' LIMIT 1", dbesc($nick) ); - if(! count($r)) + if(!(isset($r) && count($r))) return; $uid = $r[0]['uid']; @@ -213,8 +212,7 @@ function facebook_init(&$a) { /** - * @param $uid - * @return mixed + * @param int $uid */ function fb_get_self($uid) { $access_token = get_pconfig($uid,'facebook','access_token'); @@ -228,9 +226,9 @@ function fb_get_self($uid) { } /** - * @param $uid - * @param $access_token - * @param $persons + * @param int $uid + * @param string $access_token + * @param array $persons */ function fb_get_friends_sync_new($uid, $access_token, $persons) { $persons_todo = array(); @@ -252,9 +250,8 @@ function fb_get_friends_sync_new($uid, $access_token, $persons) { } /** - * @param $uid - * @param $contact - * @return mixed + * @param int $uid + * @param object $contact */ function fb_get_friends_sync_parsecontact($uid, $contact) { $contact->link = 'http://facebook.com/profile.php?id=' . $contact->id; @@ -358,10 +355,9 @@ function fb_get_friends_sync_parsecontact($uid, $contact) { } /** - * @param $uid - * @param $access_token - * @param $persons - * @return mixed + * @param int $uid + * @param string $access_token + * @param array $persons */ function fb_get_friends_sync_full($uid, $access_token, $persons) { if (count($persons) == 0) return; @@ -386,9 +382,8 @@ function fb_get_friends_sync_full($uid, $access_token, $persons) { // if $fullsync is true, only new contacts are searched for /** - * @param $uid + * @param int $uid * @param bool $fullsync - * @return mixed */ function fb_get_friends($uid, $fullsync = true) { @@ -428,8 +423,7 @@ function fb_get_friends($uid, $fullsync = true) { // Content is posted to Facebook in the function facebook_post_hook() /** - * @param $a - * @return mixed + * @param App $a */ function facebook_post(&$a) { @@ -482,7 +476,7 @@ function facebook_post(&$a) { // Facebook settings form /** - * @param $a + * @param App $a * @return string */ function facebook_content(&$a) { @@ -587,8 +581,8 @@ function facebook_content(&$a) { /** - * @param $a - * @param $b + * @param App $a + * @param null|object $b * @return mixed */ function facebook_cron($a,$b) { @@ -631,6 +625,8 @@ function facebook_cron($a,$b) { $last_friend_check = get_pconfig($rr['uid'],'facebook','friend_check'); if($last_friend_check) $next_friend_check = $last_friend_check + 86400; + else + $next_friend_check = 0; if($next_friend_check <= time()) { fb_get_friends($rr['uid'], true); set_pconfig($rr['uid'],'facebook','friend_check',time()); @@ -672,8 +668,8 @@ function facebook_cron($a,$b) { /** - * @param $a - * @param $b + * @param App $a + * @param null|object $b */ function facebook_plugin_settings(&$a,&$b) { @@ -686,8 +682,8 @@ function facebook_plugin_settings(&$a,&$b) { /** - * @param $a - * @param $o + * @param App $a + * @param null|object $o */ function facebook_plugin_admin(&$a, &$o){ @@ -734,8 +730,8 @@ function facebook_plugin_admin(&$a, &$o){ } /** - * @param $a - * @param $o + * @param App $a + * @param null|object $o */ function facebook_plugin_admin_post(&$a, &$o){ check_form_security_token_redirectOnErr('/admin/plugins/facebook', 'fbsave'); @@ -757,8 +753,8 @@ function facebook_plugin_admin_post(&$a, &$o){ } /** - * @param $a - * @param $b + * @param App $a + * @param object $b * @return mixed */ function facebook_jot_nets(&$a,&$b) { @@ -776,8 +772,8 @@ function facebook_jot_nets(&$a,&$b) { /** - * @param $a - * @param $b + * @param App $a + * @param object $b * @return mixed */ function facebook_post_hook(&$a,&$b) { @@ -798,6 +794,9 @@ function facebook_post_hook(&$a,&$b) { $reply = false; $likes = false; + $deny_arr = array(); + $allow_arr = array(); + $toplevel = (($b['id'] == $b['parent']) ? true : false); @@ -844,8 +843,7 @@ function facebook_post_hook(&$a,&$b) { $allow_str = dbesc(implode(', ',$recipients)); if($allow_str) { $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $allow_str ) AND `network` = 'face'"); - $allow_arr = array(); - if(count($r)) + if(count($r)) foreach($r as $rr) $allow_arr[] = $rr['notify']; } @@ -853,8 +851,7 @@ function facebook_post_hook(&$a,&$b) { $deny_str = dbesc(implode(', ',$deny)); if($deny_str) { $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $deny_str ) AND `network` = 'face'"); - $deny_arr = array(); - if(count($r)) + if(count($r)) foreach($r as $rr) $deny_arr[] = $rr['notify']; } @@ -1155,8 +1152,8 @@ function facebook_post_hook(&$a,&$b) { } /** - * @param $app - * @param $data + * @param App $app + * @param object $data */ function facebook_enotify(&$app, &$data) { if (x($data, 'params') && $data['params']['type'] == NOTIFY_SYSTEM && x($data['params'], 'system_type') && $data['params']['system_type'] == 'facebook_connection_invalid') { @@ -1168,9 +1165,8 @@ function facebook_enotify(&$app, &$data) { } /** - * @param $a - * @param $b - * @return mixed + * @param App $a + * @param object $b */ function facebook_post_local(&$a,&$b) { @@ -1199,9 +1195,8 @@ function facebook_post_local(&$a,&$b) { /** - * @param $a - * @param $b - * @return mixed + * @param App $a + * @param object $b */ function fb_queue_hook(&$a,&$b) { @@ -1262,9 +1257,9 @@ function fb_queue_hook(&$a,&$b) { } /** - * @param $access_token - * @param $since - * @return stdClass + * @param string $access_token + * @param int $since + * @return object */ function fb_get_timeline($access_token, &$since) { @@ -1308,8 +1303,7 @@ function fb_get_timeline($access_token, &$since) { } /** - * @param $uid - * @return mixed + * @param int $uid */ function fb_consume_all($uid) { @@ -1347,8 +1341,8 @@ function fb_consume_all($uid) { } /** - * @param $uid - * @param $link + * @param int $uid + * @param string $link * @return string */ function fb_get_photo($uid,$link) { @@ -1370,10 +1364,9 @@ function fb_get_photo($uid,$link) { } /** - * @param $uid - * @param $j + * @param int $uid + * @param object $j * @param bool $wall - * @return mixed */ function fb_consume_stream($uid,$j,$wall = false) { @@ -1767,7 +1760,7 @@ function fb_consume_stream($uid,$j,$wall = false) { /** - * @return bool|mixed|string + * @return bool|string */ function fb_get_app_access_token() { @@ -1804,9 +1797,6 @@ function fb_get_app_access_token() { } } -/** - * - */ function facebook_subscription_del_users() { $a = get_app(); $access_token = fb_get_app_access_token(); @@ -1857,7 +1847,7 @@ function facebook_subscription_add_users($second_try = false) { } /** - * @return null + * @return null|array */ function facebook_subscriptions_get() { @@ -1892,8 +1882,8 @@ function facebook_check_realtime_active() { if(! function_exists('facebook_delete_url')) { /** - * @param $url - * @param null $headers + * @param string $url + * @param null|array $headers * @param int $redirects * @param int $timeout * @return bool|string From 92e287d432834ffe5e52f79c1195adbcf6b23b80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B6=C3=9Fl?= Date: Tue, 17 Apr 2012 09:25:33 +0000 Subject: [PATCH 41/96] Remove unused variables --- facebook/facebook.php | 91 +++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 46 deletions(-) diff --git a/facebook/facebook.php b/facebook/facebook.php index dff43251..d36daf8a 100644 --- a/facebook/facebook.php +++ b/facebook/facebook.php @@ -276,7 +276,7 @@ function fb_get_friends_sync_parsecontact($uid, $contact) { $photos = import_profile_photo('https://graph.facebook.com/' . $contact->id . '/picture', $uid, $r[0]['id']); - $r = q("UPDATE `contact` SET `photo` = '%s', + q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s', `name-date` = '%s', @@ -298,7 +298,7 @@ function fb_get_friends_sync_parsecontact($uid, $contact) { else { // create contact record - $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`, + q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`, `name`, `nick`, `photo`, `network`, `rel`, `priority`, `writable`, `blocked`, `readonly`, `pending` ) VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, 0, 0, 0 ) ", @@ -329,14 +329,13 @@ function fb_get_friends_sync_parsecontact($uid, $contact) { return; } - $contact = $r[0]; $contact_id = $r[0]['id']; require_once("Photo.php"); $photos = import_profile_photo($r[0]['photo'],$uid,$contact_id); - $r = q("UPDATE `contact` SET `photo` = '%s', + q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s', `name-date` = '%s', @@ -502,7 +501,6 @@ function facebook_content(&$a) { if (get_pconfig(local_user(),'facebook','post')) { $access_token = get_pconfig(local_user(),'facebook','access_token'); if ($access_token) { - $private_wall = intval(get_pconfig(local_user(),'facebook','private_wall')); $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token); if($s) { $j = json_decode($s); @@ -647,7 +645,7 @@ function facebook_cron($a,$b) { logger('facebook_cron: Failed', LOGGER_NORMAL); if(strlen($a->config['admin_email']) && !get_config('facebook', 'realtime_err_mailsent')) { - $res = mail($a->config['admin_email'], t('Problems with Facebook Real-Time Updates'), + mail($a->config['admin_email'], t('Problems with Facebook Real-Time Updates'), "Hi!\n\nThere's a problem with the Facebook Real-Time Updates that cannot be solved automatically. Maybe a permission issue?\n\nPlease try to re-activate it on " . $a->config["system"]["url"] . "/admin/plugins/facebook\n\nThis e-mail will only be sent once.", 'From: ' . t('Administrator') . '@' . $_SERVER['SERVER_NAME'] . "\n" . 'Content-type: text/plain; charset=UTF-8' . "\n" @@ -906,8 +904,8 @@ function facebook_post_hook(&$a,&$b) { // unless it's a dislike - just send the text as a comment - if($b['verb'] == ACTIVITY_DISLIKE) - $msg = trim(strip_tags(bbcode($msg))); + // if($b['verb'] == ACTIVITY_DISLIKE) + // $msg = trim(strip_tags(bbcode($msg))); // Old code /*$search_str = $a->get_baseurl() . '/search'; @@ -1017,7 +1015,6 @@ function facebook_post_hook(&$a,&$b) { // Since facebook increased the maxpostlen massively this never should happen again :) if (strlen($msg) > FACEBOOK_MAXPOSTLEN) { - $shortlink = ""; require_once('library/slinky.php'); $display_url = $b['plink']; @@ -1361,6 +1358,7 @@ function fb_get_photo($uid,$link) { return "\n\n" . '[url=' . $link . '][img]' . $j->picture . '[/img][/url]'; //else // return "\n" . '[url=' . $link . ']' . t('link') . '[/url]'; + return ""; } /** @@ -1379,7 +1377,7 @@ function fb_consume_stream($uid,$j,$wall = false) { if(! count($user)) return; - $my_local_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname']; + // $my_local_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname']; $no_linking = get_pconfig($uid,'facebook','no_linking'); if($no_linking) @@ -1396,7 +1394,9 @@ function fb_consume_stream($uid,$j,$wall = false) { if(! count($j->data) || (! strlen($self_id))) return; - foreach($j->data as $entry) { + $top_item = 0; + + foreach($j->data as $entry) { logger('fb_consume: entry: ' . print_r($entry,true), LOGGER_DATA); $datarray = array(); @@ -1406,12 +1406,10 @@ function fb_consume_stream($uid,$j,$wall = false) { intval($uid) ); if(count($r)) { - $post_exists = true; $orig_post = $r[0]; $top_item = $r[0]['id']; } else { - $post_exists = false; $orig_post = null; } @@ -1475,32 +1473,32 @@ function fb_consume_stream($uid,$j,$wall = false) { logger('facebook: post '.$entry->id.' from '.$from->name); - $datarray['body'] = escape_tags($entry->message); + $datarray['body'] = (x($entry, 'message') ? escape_tags($entry->message) : ''); - if($entry->name and $entry->link) + if(x($entry, 'name') and x($entry, 'link')) $datarray['body'] .= "\n\n[bookmark=".$entry->link."]".$entry->name."[/bookmark]"; - elseif ($entry->name) + elseif (x($entry, 'name')) $datarray['body'] .= "\n\n[b]" . $entry->name."[/b]"; - if($entry->caption) { - if(!$entry->name and $entry->link) + if(x($entry, 'caption')) { + if(!x($entry, 'name') and x($entry, 'link')) $datarray['body'] .= "\n\n[bookmark=".$entry->link."]".$entry->caption."[/bookmark]"; else $datarray['body'] .= "[i]" . $entry->caption."[/i]\n"; } - if(!$entry->caption and !$entry->name) { - if ($entry->link) + if(!x($entry, 'caption') and !x($entry, 'name')) { + if (x($entry, 'link')) $datarray['body'] .= "\n[url]".$entry->link."[/url]\n"; else $datarray['body'] .= "\n"; } $quote = ""; - if($entry->description) + if(x($entry, 'description')) $quote = $entry->description; - if ($entry->properties) + if (x($entry, 'properties')) foreach ($entry->properties as $property) $quote .= "\n".$property->name.": [url=".$property->href."]".$property->text."[/url]"; @@ -1510,19 +1508,19 @@ function fb_consume_stream($uid,$j,$wall = false) { // Only import the picture when the message is no video // oembed display a picture of the video as well if ($entry->type != "video") { - if($entry->picture && $entry->link) { + if(x($entry, 'picture') && x($entry, 'link')) { $datarray['body'] .= "\n" . '[url=' . $entry->link . '][img]'.$entry->picture.'[/img][/url]'; } else { - if($entry->picture) + if(x($entry, 'picture')) $datarray['body'] .= "\n" . '[img]' . $entry->picture . '[/img]'; // if just a link, it may be a wall photo - check - if($entry->link) + if(x($entry, 'link')) $datarray['body'] .= fb_get_photo($uid,$entry->link); } } - if (($datarray['app'] == "Events") and $entry->actions) + if (($datarray['app'] == "Events") and x($entry, 'actions')) foreach ($entry->actions as $action) if ($action->name == "View") $datarray['body'] .= " [url=".$action->link."]".$entry->story."[/url]"; @@ -1542,10 +1540,10 @@ function fb_consume_stream($uid,$j,$wall = false) { $datarray['body'] .= "\n"; - if ($entry->icon) + if (x($entry, 'icon')) $datarray['body'] .= "[img]".$entry->icon."[/img]   "; - if ($entry->actions) + if (x($entry, 'actions')) foreach ($entry->actions as $action) if (($action->name != "Comment") and ($action->name != "Like")) $datarray['body'] .= "[url=".$action->link."]".$action->name."[/url]   "; @@ -1555,28 +1553,29 @@ function fb_consume_stream($uid,$j,$wall = false) { //if(($datarray['body'] != '') and ($uid == 1)) // $datarray['body'] .= "[noparse]".print_r($entry, true)."[/noparse]"; - if ($entry->place->name or $entry->place->location->street or - $entry->place->location->city or $entry->place->location->Denmark) { - $datarray['coord'] = ''; - if ($entry->place->name) - $datarray['coord'] .= $entry->place->name; - if ($entry->place->location->street) - $datarray['coord'] .= $entry->place->location->street; - if ($entry->place->location->city) - $datarray['coord'] .= " ".$entry->place->location->city; - if ($entry->place->location->country) - $datarray['coord'] .= " ".$entry->place->location->country; - } else if ($entry->place->location->latitude and $entry->place->location->longitude) - $datarray['coord'] = substr($entry->place->location->latitude, 0, 8) + if (x($entry, 'place')) { + if ($entry->place->name or $entry->place->location->street or + $entry->place->location->city or $entry->place->location->Denmark) { + $datarray['coord'] = ''; + if ($entry->place->name) + $datarray['coord'] .= $entry->place->name; + if ($entry->place->location->street) + $datarray['coord'] .= $entry->place->location->street; + if ($entry->place->location->city) + $datarray['coord'] .= " ".$entry->place->location->city; + if ($entry->place->location->country) + $datarray['coord'] .= " ".$entry->place->location->country; + } else if ($entry->place->location->latitude and $entry->place->location->longitude) + $datarray['coord'] = substr($entry->place->location->latitude, 0, 8) .' '.substr($entry->place->location->longitude, 0, 8); - + } $datarray['created'] = datetime_convert('UTC','UTC',$entry->created_time); $datarray['edited'] = datetime_convert('UTC','UTC',$entry->updated_time); // If the entry has a privacy policy, we cannot assume who can or cannot see it, // as the identities are from a foreign system. Mark it as private to the owner. - if($entry->privacy && $entry->privacy->value !== 'EVERYONE') { + if(x($entry, 'privacy') && $entry->privacy->value !== 'EVERYONE') { $datarray['private'] = 1; $datarray['allow_cid'] = '<' . $self[0]['id'] . '>'; } @@ -1592,12 +1591,12 @@ function fb_consume_stream($uid,$j,$wall = false) { } } - if(isset($entry->likes) && isset($entry->likes->data)) + if(x($entry, 'likes') && x($entry->likes, 'data')) $likers = $entry->likes->data; else $likers = null; - if(isset($entry->comments) && isset($entry->comments->data)) + if(x($entry, 'comments') && x($entry->comments, 'data')) $comments = $entry->comments->data; else $comments = null; @@ -1661,7 +1660,7 @@ function fb_consume_stream($uid,$j,$wall = false) { $likedata['object'] = '' . ACTIVITY_OBJ_NOTE . '1' . '' . $orig_post['uri'] . '' . xmlify('') . '' . $orig_post['title'] . '' . $orig_post['body'] . ''; - $item = item_store($likedata); + item_store($likedata); } } if(is_array($comments)) { From 5911b0b1132896ebd3d3643faad82a31ccae9af8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B6=C3=9Fl?= Date: Tue, 17 Apr 2012 15:38:37 +0000 Subject: [PATCH 42/96] Fixing a bug and a bug so huge it could get a leading role in Starship Troopers... sorry ;_; --- facebook/facebook.php | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/facebook/facebook.php b/facebook/facebook.php index d36daf8a..4f534a0b 100644 --- a/facebook/facebook.php +++ b/facebook/facebook.php @@ -1289,7 +1289,7 @@ function fb_get_timeline($access_token, &$since) { else break; - $url = $j->paging->next; + $url = (isset($j->paging) && isset($j->paging->next) ? $j->paging->next : ''); } while (($oldestdate > $since) and ($since != 0) and ($url != '')); @@ -1424,7 +1424,7 @@ function fb_consume_stream($uid,$j,$wall = false) { else { // Looking if user is known - if not he is added $access_token = get_pconfig($uid, 'facebook', 'access_token'); - fb_get_friends_sync_new($uid, $access_token, $from); + fb_get_friends_sync_new($uid, $access_token, array($from)); $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1", dbesc($from->id), @@ -1473,32 +1473,32 @@ function fb_consume_stream($uid,$j,$wall = false) { logger('facebook: post '.$entry->id.' from '.$from->name); - $datarray['body'] = (x($entry, 'message') ? escape_tags($entry->message) : ''); + $datarray['body'] = (isset($entry->message) ? escape_tags($entry->message) : ''); - if(x($entry, 'name') and x($entry, 'link')) + if(isset($entry->name) and isset($entry->link)) $datarray['body'] .= "\n\n[bookmark=".$entry->link."]".$entry->name."[/bookmark]"; - elseif (x($entry, 'name')) + elseif (isset($entry->name)) $datarray['body'] .= "\n\n[b]" . $entry->name."[/b]"; - if(x($entry, 'caption')) { - if(!x($entry, 'name') and x($entry, 'link')) + if(isset($entry->caption)) { + if(!isset($entry->name) and isset($entry->link)) $datarray['body'] .= "\n\n[bookmark=".$entry->link."]".$entry->caption."[/bookmark]"; else $datarray['body'] .= "[i]" . $entry->caption."[/i]\n"; } - if(!x($entry, 'caption') and !x($entry, 'name')) { - if (x($entry, 'link')) + if(!isset($entry->caption) and !isset($entry->name)) { + if (isset($entry->link)) $datarray['body'] .= "\n[url]".$entry->link."[/url]\n"; else $datarray['body'] .= "\n"; } $quote = ""; - if(x($entry, 'description')) + if(isset($entry->description)) $quote = $entry->description; - if (x($entry, 'properties')) + if (isset($entry->properties)) foreach ($entry->properties as $property) $quote .= "\n".$property->name.": [url=".$property->href."]".$property->text."[/url]"; @@ -1508,19 +1508,19 @@ function fb_consume_stream($uid,$j,$wall = false) { // Only import the picture when the message is no video // oembed display a picture of the video as well if ($entry->type != "video") { - if(x($entry, 'picture') && x($entry, 'link')) { + if(isset($entry->picture) && isset($entry->link)) { $datarray['body'] .= "\n" . '[url=' . $entry->link . '][img]'.$entry->picture.'[/img][/url]'; } else { - if(x($entry, 'picture')) + if(isset($entry->picture)) $datarray['body'] .= "\n" . '[img]' . $entry->picture . '[/img]'; // if just a link, it may be a wall photo - check - if(x($entry, 'link')) + if(isset($entry->link)) $datarray['body'] .= fb_get_photo($uid,$entry->link); } } - if (($datarray['app'] == "Events") and x($entry, 'actions')) + if (($datarray['app'] == "Events") and isset($entry->actions)) foreach ($entry->actions as $action) if ($action->name == "View") $datarray['body'] .= " [url=".$action->link."]".$entry->story."[/url]"; @@ -1540,10 +1540,10 @@ function fb_consume_stream($uid,$j,$wall = false) { $datarray['body'] .= "\n"; - if (x($entry, 'icon')) + if (isset($entry->icon)) $datarray['body'] .= "[img]".$entry->icon."[/img]   "; - if (x($entry, 'actions')) + if (isset($entry->actions)) foreach ($entry->actions as $action) if (($action->name != "Comment") and ($action->name != "Like")) $datarray['body'] .= "[url=".$action->link."]".$action->name."[/url]   "; @@ -1553,7 +1553,7 @@ function fb_consume_stream($uid,$j,$wall = false) { //if(($datarray['body'] != '') and ($uid == 1)) // $datarray['body'] .= "[noparse]".print_r($entry, true)."[/noparse]"; - if (x($entry, 'place')) { + if (isset($entry->place)) { if ($entry->place->name or $entry->place->location->street or $entry->place->location->city or $entry->place->location->Denmark) { $datarray['coord'] = ''; @@ -1575,7 +1575,7 @@ function fb_consume_stream($uid,$j,$wall = false) { // If the entry has a privacy policy, we cannot assume who can or cannot see it, // as the identities are from a foreign system. Mark it as private to the owner. - if(x($entry, 'privacy') && $entry->privacy->value !== 'EVERYONE') { + if(isset($entry->privacy) && $entry->privacy->value !== 'EVERYONE') { $datarray['private'] = 1; $datarray['allow_cid'] = '<' . $self[0]['id'] . '>'; } @@ -1591,12 +1591,12 @@ function fb_consume_stream($uid,$j,$wall = false) { } } - if(x($entry, 'likes') && x($entry->likes, 'data')) + if(isset($entry->likes) && isset($entry->likes->data)) $likers = $entry->likes->data; else $likers = null; - if(x($entry, 'comments') && x($entry->comments, 'data')) + if(isset($entry->comments) && isset($entry->comments->data)) $comments = $entry->comments->data; else $comments = null; From 4a460ed078f16a67731ddd4fc6c230ea8f960b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B6=C3=9Fl?= Date: Tue, 17 Apr 2012 18:07:49 +0000 Subject: [PATCH 43/96] Synchronizing comments and likes of individual FB-comments --- facebook/facebook.php | 396 ++++++++++++++++++++++++++---------------- 1 file changed, 244 insertions(+), 152 deletions(-) diff --git a/facebook/facebook.php b/facebook/facebook.php index 4f534a0b..9501d95d 100644 --- a/facebook/facebook.php +++ b/facebook/facebook.php @@ -1,7 +1,7 @@ * Tobias Hößl */ @@ -516,7 +516,7 @@ function facebook_content(&$a) { return ''; } - $a->page['htmlhead'] .= '' . "\r\n"; $o .= '

' . t('Facebook Connect') . '

'; @@ -693,6 +693,7 @@ function facebook_plugin_admin(&$a, &$o){ $appid = get_config('facebook', 'appid' ); $appsecret = get_config('facebook', 'appsecret' ); $poll_interval = get_config('facebook', 'poll_interval' ); + $sync_comments = get_config('facebook', 'sync_comments' ); if (!$poll_interval) $poll_interval = FACEBOOK_DEFAULT_POLL_INTERVAL; $ret1 = q("SELECT `v` FROM `config` WHERE `cat` = 'facebook' AND `k` = 'appid' LIMIT 1"); @@ -712,6 +713,7 @@ function facebook_plugin_admin(&$a, &$o){ $o .= '
'; $o .= '
'; $o .= '
'; + $o .= '
'; $o .= ''; if ($working_connection) { @@ -739,6 +741,7 @@ function facebook_plugin_admin_post(&$a, &$o){ set_config('facebook', 'appsecret', $_REQUEST['appsecret']); $poll_interval = IntVal($_REQUEST['poll_interval']); if ($poll_interval >= FACEBOOK_MIN_POLL_INTERVAL) set_config('facebook', 'poll_interval', $poll_interval); + set_config('facebook', 'sync_comments', (x($_REQUEST, 'sync_comments') ? 1 : 0)); del_config('facebook', 'app_access_token'); info(t('The new values have been saved.')); } @@ -1324,7 +1327,7 @@ function fb_consume_all($uid) { } } // Get the last date - $lastdate = get_pconfig($uid,'facebook','lastdate'); + $lastdate = get_pconfig($uid,'facebook','lastdate') - 10000; // fetch all items since the last date $j = fb_get_timeline($access_token, $lastdate); if (isset($j->data)) { @@ -1361,6 +1364,209 @@ function fb_get_photo($uid,$link) { return ""; } + +/** + * @param App $a + * @param array $user + * @param array $self + * @param string $fb_id + * @param bool $wall + * @param array $orig_post + * @param object $cmnt + */ +function fb_consume_comment(&$a, &$user, &$self, $fb_id, $wall, &$orig_post, &$cmnt) { + + if(! $orig_post) + return; + + $top_item = $orig_post['id']; + $uid = IntVal($user[0]['uid']); + + $r = q("SELECT * FROM `item` WHERE `uid` = %d AND ( `uri` = '%s' OR `extid` = '%s' ) LIMIT 1", + intval($uid), + dbesc('fb::' . $cmnt->id), + dbesc('fb::' . $cmnt->id) + ); + if(count($r)) + return; + + $cmntdata = array(); + $cmntdata['parent'] = $top_item; + $cmntdata['verb'] = ACTIVITY_POST; + $cmntdata['gravity'] = 6; + $cmntdata['uid'] = $uid; + $cmntdata['wall'] = (($wall) ? 1 : 0); + $cmntdata['uri'] = 'fb::' . $cmnt->id; + $cmntdata['parent-uri'] = $orig_post['uri']; + if($cmnt->from->id == $fb_id) { + $cmntdata['contact-id'] = $self[0]['id']; + } + else { + $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d LIMIT 1", + dbesc($cmnt->from->id), + intval($uid) + ); + if(count($r)) { + $cmntdata['contact-id'] = $r[0]['id']; + if($r[0]['blocked'] || $r[0]['readonly']) + return; + } + } + if(! x($cmntdata,'contact-id')) + $cmntdata['contact-id'] = $orig_post['contact-id']; + + $cmntdata['app'] = 'facebook'; + $cmntdata['created'] = datetime_convert('UTC','UTC',$cmnt->created_time); + $cmntdata['edited'] = datetime_convert('UTC','UTC',$cmnt->created_time); + $cmntdata['verb'] = ACTIVITY_POST; + $cmntdata['author-name'] = $cmnt->from->name; + $cmntdata['author-link'] = 'http://facebook.com/profile.php?id=' . $cmnt->from->id; + $cmntdata['author-avatar'] = 'https://graph.facebook.com/' . $cmnt->from->id . '/picture'; + $cmntdata['body'] = $cmnt->message; + $item = item_store($cmntdata); + + $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 ", + dbesc($orig_post['uri']), + intval($uid) + ); + + if(count($myconv)) { + $importer_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname']; + + foreach($myconv as $conv) { + + // now if we find a match, it means we're in this conversation + + if(! link_compare($conv['author-link'],$importer_url)) + continue; + + require_once('include/enotify.php'); + + $conv_parent = $conv['parent']; + + notification(array( + 'type' => NOTIFY_COMMENT, + 'notify_flags' => $user[0]['notify-flags'], + 'language' => $user[0]['language'], + 'to_name' => $user[0]['username'], + 'to_email' => $user[0]['email'], + 'uid' => $user[0]['uid'], + 'item' => $cmntdata, + 'link' => $a->get_baseurl() . '/display/' . $user[0]['nickname'] . '/' . $item, + 'source_name' => $cmntdata['author-name'], + 'source_link' => $cmntdata['author-link'], + 'source_photo' => $cmntdata['author-avatar'], + 'verb' => ACTIVITY_POST, + 'otype' => 'item', + 'parent' => $conv_parent, + )); + + // only send one notification + break; + } + } +} + + +/** + * @param App $a + * @param array $user + * @param array $self + * @param string $fb_id + * @param bool $wall + * @param array $orig_post + * @param object $likes + */ +function fb_consume_like(&$a, &$user, &$self, $fb_id, $wall, &$orig_post, &$likes) { + + $top_item = $orig_post['id']; + $uid = IntVal($user[0]['uid']); + + if(! $orig_post) + return; + + // If we posted the like locally, it will be found with our url, not the FB url. + + $second_url = (($likes->id == $fb_id) ? $self[0]['url'] : 'http://facebook.com/profile.php?id=' . $likes->id); + + $r = q("SELECT * FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `verb` = '%s' + AND ( `author-link` = '%s' OR `author-link` = '%s' ) LIMIT 1", + dbesc($orig_post['uri']), + intval($uid), + dbesc(ACTIVITY_LIKE), + dbesc('http://facebook.com/profile.php?id=' . $likes->id), + dbesc($second_url) + ); + + if(count($r)) + return; + + $likedata = array(); + $likedata['parent'] = $top_item; + $likedata['verb'] = ACTIVITY_LIKE; + $likedata['gravity'] = 3; + $likedata['uid'] = $uid; + $likedata['wall'] = (($wall) ? 1 : 0); + $likedata['uri'] = item_new_uri($a->get_baseurl(), $uid); + $likedata['parent-uri'] = $orig_post['uri']; + if($likes->id == $fb_id) + $likedata['contact-id'] = $self[0]['id']; + else { + $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1", + dbesc($likes->id), + intval($uid) + ); + if(count($r)) + $likedata['contact-id'] = $r[0]['id']; + } + if(! x($likedata,'contact-id')) + $likedata['contact-id'] = $orig_post['contact-id']; + + $likedata['app'] = 'facebook'; + $likedata['verb'] = ACTIVITY_LIKE; + $likedata['author-name'] = $likes->name; + $likedata['author-link'] = 'http://facebook.com/profile.php?id=' . $likes->id; + $likedata['author-avatar'] = 'https://graph.facebook.com/' . $likes->id . '/picture'; + + $author = '[url=' . $likedata['author-link'] . ']' . $likedata['author-name'] . '[/url]'; + $objauthor = '[url=' . $orig_post['author-link'] . ']' . $orig_post['author-name'] . '[/url]'; + $post_type = t('status'); + $plink = '[url=' . $orig_post['plink'] . ']' . $post_type . '[/url]'; + $likedata['object-type'] = ACTIVITY_OBJ_NOTE; + + $likedata['body'] = sprintf( t('%1$s likes %2$s\'s %3$s'), $author, $objauthor, $plink); + $likedata['object'] = '' . ACTIVITY_OBJ_NOTE . '1' . + '' . $orig_post['uri'] . '' . xmlify('') . '' . $orig_post['title'] . '' . $orig_post['body'] . ''; + + item_store($likedata); +} + +/** + * @param App $a + * @param array $user + * @param object $entry + * @param array $self + * @param string $fb_id + * @param bool $wall + * @param array $orig_post + */ +function fb_consume_status(&$a, &$user, &$entry, &$self, $fb_id, $wall, &$orig_post) { + $uid = IntVal($user[0]['uid']); + $access_token = get_pconfig($uid, 'facebook', 'access_token'); + + $s = fetch_url('https://graph.facebook.com/' . $entry->id . '?access_token=' . $access_token); + if($s) { + $j = json_decode($s); + if (isset($j->comments) && isset($j->comments->data)) + foreach ($j->comments->data as $cmnt) + fb_consume_comment($a, $user, $self, $fb_id, $wall, $orig_post, $cmnt); + + if (isset($j->likes) && isset($j->likes->data)) + foreach ($j->likes->data as $likers) + fb_consume_like($a, $user, $self, $fb_id, $wall, $orig_post, $likers); + } +} + /** * @param int $uid * @param object $j @@ -1370,7 +1576,6 @@ function fb_consume_stream($uid,$j,$wall = false) { $a = get_app(); - $user = q("SELECT * FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1", intval($uid) ); @@ -1390,6 +1595,9 @@ function fb_consume_stream($uid,$j,$wall = false) { $blocked_apps = get_pconfig($uid,'facebook','blocked_apps'); $blocked_apps_arr = explode(',',$blocked_apps); + $sync_comments = get_config('facebook', 'sync_comments'); + + /** @var string $self_id */ $self_id = get_pconfig($uid,'facebook','self_id'); if(! count($j->data) || (! strlen($self_id))) return; @@ -1591,169 +1799,53 @@ function fb_consume_stream($uid,$j,$wall = false) { } } + /** @var array $orig_post */ + + $likers_num = (isset($entry->likes) && isset($entry->likes->count) ? IntVal($entry->likes->count) : 0 ); if(isset($entry->likes) && isset($entry->likes->data)) $likers = $entry->likes->data; else $likers = null; + $comments_num = (isset($entry->comments) && isset($entry->comments->count) ? IntVal($entry->comments->count) : 0 ); if(isset($entry->comments) && isset($entry->comments->data)) $comments = $entry->comments->data; else $comments = null; - if(is_array($likers)) { - foreach($likers as $likes) { + $needs_sync = false; - if(! $orig_post) - continue; - - // If we posted the like locally, it will be found with our url, not the FB url. - - $second_url = (($likes->id == $self_id) ? $self[0]['url'] : 'http://facebook.com/profile.php?id=' . $likes->id); - - $r = q("SELECT * FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `verb` = '%s' - AND ( `author-link` = '%s' OR `author-link` = '%s' ) LIMIT 1", - dbesc($orig_post['uri']), - intval($uid), - dbesc(ACTIVITY_LIKE), - dbesc('http://facebook.com/profile.php?id=' . $likes->id), - dbesc($second_url) - ); - - if(count($r)) - continue; - - $likedata = array(); - $likedata['parent'] = $top_item; - $likedata['verb'] = ACTIVITY_LIKE; - $likedata['gravity'] = 3; - $likedata['uid'] = $uid; - $likedata['wall'] = (($wall) ? 1 : 0); - $likedata['uri'] = item_new_uri($a->get_baseurl(), $uid); - $likedata['parent-uri'] = $orig_post['uri']; - if($likes->id == $self_id) - $likedata['contact-id'] = $self[0]['id']; - else { - $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1", - dbesc($likes->id), - intval($uid) - ); - if(count($r)) - $likedata['contact-id'] = $r[0]['id']; - } - if(! x($likedata,'contact-id')) - $likedata['contact-id'] = $orig_post['contact-id']; - - $likedata['app'] = 'facebook'; - $likedata['verb'] = ACTIVITY_LIKE; - $likedata['author-name'] = $likes->name; - $likedata['author-link'] = 'http://facebook.com/profile.php?id=' . $likes->id; - $likedata['author-avatar'] = 'https://graph.facebook.com/' . $likes->id . '/picture'; - - $author = '[url=' . $likedata['author-link'] . ']' . $likedata['author-name'] . '[/url]'; - $objauthor = '[url=' . $orig_post['author-link'] . ']' . $orig_post['author-name'] . '[/url]'; - $post_type = t('status'); - $plink = '[url=' . $orig_post['plink'] . ']' . $post_type . '[/url]'; - $likedata['object-type'] = ACTIVITY_OBJ_NOTE; - - $likedata['body'] = sprintf( t('%1$s likes %2$s\'s %3$s'), $author, $objauthor, $plink); - $likedata['object'] = '' . ACTIVITY_OBJ_NOTE . '1' . - '' . $orig_post['uri'] . '' . xmlify('') . '' . $orig_post['title'] . '' . $orig_post['body'] . ''; - - item_store($likedata); - } + if(is_array($likers)) { + foreach($likers as $likes) fb_consume_like($a, $user, $self, $self_id, $wall, $orig_post, $likes); + if ($sync_comments) { + $r = q("SELECT COUNT(*) likes FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `verb` = '%s' AND `parent-uri` != `uri`", + dbesc($orig_post['uri']), + intval($uid), + dbesc(ACTIVITY_LIKE) + ); + if ($r[0]['likes'] < $likers_num) { + logger('fb_consume_stream: missing likes found for ' . $orig_post['uri'] . ' (we have ' . $r[0]['likes'] . ' of ' . $likers_num . '). Synchronizing...', LOGGER_DEBUG); + $needs_sync = true; + } + } } + if(is_array($comments)) { - foreach($comments as $cmnt) { - - if(! $orig_post) - continue; - - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND ( `uri` = '%s' OR `extid` = '%s' ) LIMIT 1", - intval($uid), - dbesc('fb::' . $cmnt->id), - dbesc('fb::' . $cmnt->id) - ); - if(count($r)) - continue; - - $cmntdata = array(); - $cmntdata['parent'] = $top_item; - $cmntdata['verb'] = ACTIVITY_POST; - $cmntdata['gravity'] = 6; - $cmntdata['uid'] = $uid; - $cmntdata['wall'] = (($wall) ? 1 : 0); - $cmntdata['uri'] = 'fb::' . $cmnt->id; - $cmntdata['parent-uri'] = $orig_post['uri']; - if($cmnt->from->id == $self_id) { - $cmntdata['contact-id'] = $self[0]['id']; - } - else { - $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d LIMIT 1", - dbesc($cmnt->from->id), - intval($uid) - ); - if(count($r)) { - $cmntdata['contact-id'] = $r[0]['id']; - if($r[0]['blocked'] || $r[0]['readonly']) - continue; - } - } - if(! x($cmntdata,'contact-id')) - $cmntdata['contact-id'] = $orig_post['contact-id']; - - $cmntdata['app'] = 'facebook'; - $cmntdata['created'] = datetime_convert('UTC','UTC',$cmnt->created_time); - $cmntdata['edited'] = datetime_convert('UTC','UTC',$cmnt->created_time); - $cmntdata['verb'] = ACTIVITY_POST; - $cmntdata['author-name'] = $cmnt->from->name; - $cmntdata['author-link'] = 'http://facebook.com/profile.php?id=' . $cmnt->from->id; - $cmntdata['author-avatar'] = 'https://graph.facebook.com/' . $cmnt->from->id . '/picture'; - $cmntdata['body'] = $cmnt->message; - $item = item_store($cmntdata); - - $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 ", - dbesc($orig_post['uri']), - intval($uid) - ); - - if(count($myconv)) { - $importer_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname']; - - foreach($myconv as $conv) { - - // now if we find a match, it means we're in this conversation - - if(! link_compare($conv['author-link'],$importer_url)) - continue; - - require_once('include/enotify.php'); - - $conv_parent = $conv['parent']; - - notification(array( - 'type' => NOTIFY_COMMENT, - 'notify_flags' => $user[0]['notify-flags'], - 'language' => $user[0]['language'], - 'to_name' => $user[0]['username'], - 'to_email' => $user[0]['email'], - 'uid' => $user[0]['uid'], - 'item' => $cmntdata, - 'link' => $a->get_baseurl() . '/display/' . $user[0]['nickname'] . '/' . $item, - 'source_name' => $cmntdata['author-name'], - 'source_link' => $cmntdata['author-link'], - 'source_photo' => $cmntdata['author-avatar'], - 'verb' => ACTIVITY_POST, - 'otype' => 'item', - 'parent' => $conv_parent, - )); - - // only send one notification - break; - } - } + foreach($comments as $cmnt) fb_consume_comment($a, $user, $self, $self_id, $wall, $orig_post, $cmnt); + if ($sync_comments) { + $r = q("SELECT COUNT(*) comments FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `verb` = '%s' AND `parent-uri` != `uri`", + dbesc($orig_post['uri']), + intval($uid), + ACTIVITY_POST + ); + if ($r[0]['comments'] < $comments_num) { + logger('fb_consume_stream: missing comments found for ' . $orig_post['uri'] . ' (we have ' . $r[0]['comments'] . ' of ' . $comments_num . '). Synchronizing...', LOGGER_DEBUG); + $needs_sync = true; + } } } + + if ($needs_sync) fb_consume_status($a, $user, $entry, $self, $self_id, $wall, $orig_post); } } From 4018885640d3864a7d4039f4a3ec297e8c8443b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B6=C3=9Fl?= Date: Tue, 17 Apr 2012 18:18:58 +0000 Subject: [PATCH 44/96] Synchronizing comments and likes of individual FB-comments --- facebook/facebook.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/facebook/facebook.php b/facebook/facebook.php index 9501d95d..aa388a17 100644 --- a/facebook/facebook.php +++ b/facebook/facebook.php @@ -1327,7 +1327,7 @@ function fb_consume_all($uid) { } } // Get the last date - $lastdate = get_pconfig($uid,'facebook','lastdate') - 10000; + $lastdate = get_pconfig($uid,'facebook','lastdate'); // fetch all items since the last date $j = fb_get_timeline($access_token, $lastdate); if (isset($j->data)) { From fccaea4f2f9cb29fed49e5c1264b7a55773c6451 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Tue, 17 Apr 2012 21:00:03 +0200 Subject: [PATCH 45/96] jappixmini: do not annoy user by asking multiple times for authorization --- jappixmini/lib.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jappixmini/lib.js b/jappixmini/lib.js index bbbc7e34..91ba6f6a 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -120,6 +120,9 @@ function jappixmini_manage_roster(contacts, autoapprove, autosubscribe) { message = "Accept "+xid+" for chat?"; if (pstatus) message += "\n\nStatus:\n"+pstatus; approve = confirm(message); + + // do not ask any more + if (!approve) sendSubscribe(xid, "unsubscribed"); } if (approve) { From d09db57f8897dc5297b15f439baf829ceb1ee8ad Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Tue, 17 Apr 2012 21:13:31 +0200 Subject: [PATCH 46/96] jappixmini: try to fix priority --- jappixmini/lib.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jappixmini/lib.js b/jappixmini/lib.js index 91ba6f6a..41773e95 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -235,9 +235,9 @@ function jappixmini_addon_start(server, username, proxy, bosh, encrypted, passwo LOCK_HOST = "off"; launchMini(true, false, server, username, password); - // increase priority over other Jabber clients + // increase priority over other Jabber clients - does not seem to work? priority = 101; - sendPresence(null,null,priority); + presenceMini(null,null,priority); jappixmini_manage_roster(contacts, autoapprove, autosubscribe) } From cd28f9d7cce5b0ffbc00e03890c791ca83724602 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Tue, 17 Apr 2012 21:14:28 +0200 Subject: [PATCH 47/96] jappixmini: also add to roster if no name set --- jappixmini/lib.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jappixmini/lib.js b/jappixmini/lib.js index 41773e95..a270a28f 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -126,6 +126,9 @@ function jappixmini_manage_roster(contacts, autoapprove, autosubscribe) { } if (approve) { + name = contacts[xid]; + if (!name) name = xid; + acceptSubscribe(xid, contacts[xid]); console.log("Accepted "+xid+" for chat."); } From 9c7b38fb4aea603b62ad5af8c32e841791521001 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Tue, 17 Apr 2012 21:15:32 +0200 Subject: [PATCH 48/96] jappixmini: improve logging --- jappixmini/lib.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/jappixmini/lib.js b/jappixmini/lib.js index a270a28f..1f2586b3 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -137,6 +137,8 @@ function jappixmini_manage_roster(contacts, autoapprove, autosubscribe) { // autosubscribe if (!autosubscribe) return; + console.log("Start autosubscribe."); + var get_roster = new JSJaCIQ(); get_roster.setType('get'); get_roster.setQuery(NS_ROSTER); @@ -161,7 +163,6 @@ function jappixmini_manage_roster(contacts, autoapprove, autosubscribe) { }); if ($.inArray("Friendica", groups)==-1) { - console.log("Add "+xid+" to Friendica group."); groups.push("Friendica"); sendRoster(xid, null, null, groups); console.log("Added "+xid+" to Friendica group."); @@ -170,6 +171,7 @@ function jappixmini_manage_roster(contacts, autoapprove, autosubscribe) { // authorize if necessary if (subscription=="to") { sendSubscribe(xid, 'subscribed'); + console.log("Authorized "+xid+" automatically."); } // remove from list @@ -188,7 +190,7 @@ function jappixmini_manage_roster(contacts, autoapprove, autosubscribe) { presence.setStatus("I'm "+MINI_NICKNAME+" from ~Friendica.\n[machine-generated message]"); con.send(presence); - console.log("subscribed to "+xid); + console.log("Subscribed to "+xid+" automatically."); // add to roster var iq = new JSJaCIQ(); @@ -198,9 +200,11 @@ function jappixmini_manage_roster(contacts, autoapprove, autosubscribe) { item.setAttribute('name', contacts[xid]); item.appendChild(iq.buildNode('group', {'xmlns': NS_ROSTER}, "Friendica")); con.send(iq); - console.log("added to roster: "+xid); + console.log("Added "+xid+" to roster."); } + console.log("Autosubscribe done."); }); + } function jappixmini_addon_subscribe() { From fb7f2b1122c19eae058482ac4c86cf0d9f55d082 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Tue, 17 Apr 2012 21:18:11 +0200 Subject: [PATCH 49/96] jappixmini: autosubscribe only when contacts list changed --- jappixmini/jappixmini.php | 3 ++- jappixmini/lib.js | 32 +++++++++++++++++++++----------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/jappixmini/jappixmini.php b/jappixmini/jappixmini.php index 6840c1d9..4c3ebcc4 100644 --- a/jappixmini/jappixmini.php +++ b/jappixmini/jappixmini.php @@ -458,6 +458,7 @@ function jappixmini_script(&$a,&$s) { $contacts[$address] = $name; } $contacts_json = json_encode($contacts); + $contacts_hash = sha1($contacts_json); // get nickname $r = q("SELECT `username` FROM `user` WHERE `uid`=$uid"); @@ -466,7 +467,7 @@ function jappixmini_script(&$a,&$s) { // add javascript to start Jappix Mini $a->page['htmlhead'] .= ""; diff --git a/jappixmini/lib.js b/jappixmini/lib.js index 1f2586b3..6b7031e6 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -91,7 +91,7 @@ function jappixmini_addon_decrypt_password(encrypted_password, callback) { }); } -function jappixmini_manage_roster(contacts, autoapprove, autosubscribe) { +function jappixmini_manage_roster(contacts, contacts_hash, autoapprove, autosubscribe) { // listen for subscriptions con.registerHandler('presence',function(presence){ var type = presence.getType(); @@ -137,6 +137,10 @@ function jappixmini_manage_roster(contacts, autoapprove, autosubscribe) { // autosubscribe if (!autosubscribe) return; + stored_hash = getPersistent("jappix-mini", "contacts-hash"); + contacts_changed = (stored_hash != contacts_hash); // stored_hash gets updated later if everything was successful + if (!contacts_changed) return; + console.log("Start autosubscribe."); var get_roster = new JSJaCIQ(); @@ -202,6 +206,8 @@ function jappixmini_manage_roster(contacts, autoapprove, autosubscribe) { con.send(iq); console.log("Added "+xid+" to roster."); } + + setPersistent("jappix-mini", "contacts-hash", contacts_hash); console.log("Autosubscribe done."); }); @@ -217,7 +223,7 @@ function jappixmini_addon_subscribe() { sendSubscribe(xid, "subscribe"); } -function jappixmini_addon_start(server, username, proxy, bosh, encrypted, password, nickname, contacts, autoapprove, autosubscribe) { +function jappixmini_addon_start(server, username, proxy, bosh, encrypted, password, nickname, contacts, contacts_hash, autoapprove, autosubscribe) { handler = function(password){ // check if settings have changed, reinitialize jappix mini if this is the case settings_identifier = str_sha1(server); @@ -227,15 +233,19 @@ function jappixmini_addon_start(server, username, proxy, bosh, encrypted, passwo settings_identifier += str_sha1(password); settings_identifier += str_sha1(nickname); - saved_identifier = getDB("jappix-mini", "settings_identifier"); - if (saved_identifier != settings_identifier) removeDB('jappix-mini', 'dom'); - setDB("jappix-mini", "settings_identifier", settings_identifier); + saved_identifier = getDB("jappix-mini", "settings-identifier"); + if (saved_identifier != settings_identifier) { + disconnectMini(); + removeDB('jappix-mini', 'dom'); + removePersistent("jappix-mini", "contacts-hash"); + } + setDB("jappix-mini", "settings-identifier", settings_identifier); - // set HOST_BOSH - if (proxy) - HOST_BOSH = proxy+"?host_bosh="+encodeURI(bosh); - else - HOST_BOSH = bosh; + // set HOST_BOSH + if (proxy) + HOST_BOSH = proxy+"?host_bosh="+encodeURI(bosh); + else + HOST_BOSH = bosh; // start jappix mini MINI_NICKNAME = nickname; @@ -246,7 +256,7 @@ function jappixmini_addon_start(server, username, proxy, bosh, encrypted, passwo priority = 101; presenceMini(null,null,priority); - jappixmini_manage_roster(contacts, autoapprove, autosubscribe) + jappixmini_manage_roster(contacts, contacts_hash, autoapprove, autosubscribe) } // decrypt password if necessary From 6846c0658b4f20e27c062f9e11f86a98f681a3cd Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Tue, 17 Apr 2012 22:17:06 +0200 Subject: [PATCH 50/96] jappixmini: display number of addresses/contacts in user settings --- jappixmini/jappixmini.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/jappixmini/jappixmini.php b/jappixmini/jappixmini.php index 4c3ebcc4..d09427e7 100644 --- a/jappixmini/jappixmini.php +++ b/jappixmini/jappixmini.php @@ -257,6 +257,16 @@ function jappixmini_settings(&$a, &$s) { $info_text = htmlentities($info_text); $info_text = str_replace("\n", "
", $info_text); + // count contacts + $r = q("SELECT COUNT(1) as `cnt` FROM `pconfig` WHERE `uid`=%d AND `cat`='jappixmini' AND `k` LIKE 'id:%%'", local_user()); + if (count($r)) $contact_cnt = $r[0]["cnt"]; + else $contact_cnt = 0; + + // count jabber addresses + $r = q("SELECT COUNT(1) as `cnt` FROM `pconfig` WHERE `uid`=%d AND `cat`='jappixmini' AND `k` LIKE 'id:%%' AND `v` LIKE '%%@%%'", local_user()); + if (count($r)) $address_cnt = $r[0]["cnt"]; + else $address_cnt = 0; + if (!$activate) { // load scripts if not yet activated so that password can be saved $a->page['htmlhead'] .= ''."\r\n"; @@ -304,6 +314,7 @@ function jappixmini_settings(&$a, &$s) { $s .= ' '; $s .= '
'; if ($info_text) $s .= '
Configuration help:

'.$info_text.'

'; + $s .= '
Status:

Addon knows '.$address_cnt.' Jabber addresses of '.$contact_cnt.' Friendica contacts (takes some time, usually 10 minutes, to update).

'; $s .= ''; $s .= ' '; $s .= ''; @@ -390,7 +401,7 @@ function jappixmini_settings_post(&$a,&$b) { info( 'Jappix Mini settings saved.' ); if ($purge) { - q("DELETE FROM `pconfig` WHERE `uid`=$uid AND `cat`='jappixmini' and `k` LIKE 'id%%'"); + q("DELETE FROM `pconfig` WHERE `uid`=$uid AND `cat`='jappixmini' AND `k` LIKE 'id:%%'"); info( 'List of addresses purged.' ); } } @@ -438,7 +449,7 @@ function jappixmini_script(&$a,&$s) { // get a list of jabber accounts of the contacts $contacts = Array(); $uid = local_user(); - $rows = q("SELECT * FROM `pconfig` WHERE `uid`=$uid AND `cat`='jappixmini' and `k` LIKE 'id%%'"); + $rows = q("SELECT * FROM `pconfig` WHERE `uid`=$uid AND `cat`='jappixmini' AND `k` LIKE 'id:%%'"); foreach ($rows as $row) { $key = $row['k']; $pos = strpos($key, ":"); From 2fef298b3e08bc4a332dc53976ca2e69547ad318 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Tue, 17 Apr 2012 22:32:54 +0200 Subject: [PATCH 51/96] jappixmini: spare global js namespace --- jappixmini/lib.js | 79 ++++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/jappixmini/lib.js b/jappixmini/lib.js index 6b7031e6..ca0130a9 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -1,9 +1,9 @@ function jappixmini_addon_xor(str1, str2) { if (str1.length != str2.length) throw "not same length"; - encoded = ""; + var encoded = ""; - for (i=0; iRetype your Friendica password for chatting:
'); - input = $('') + var input = $('') div.append(input); - button = $(''); + var button = $(''); div.append(button); $("body").append(div); } button.click(function(){ - password = $("#jappixmini-password-query-input").val(); + var password = $("#jappixmini-password-query-input").val(); jappixmini_addon_set_client_secret(password); div.remove(); - client_secret = getPersistent('jappix-mini', 'client-secret'); + var client_secret = getPersistent('jappix-mini', 'client-secret'); callback(client_secret); }); } @@ -68,7 +68,7 @@ function jappixmini_addon_encrypt_password(password, callback) { } // xor password with secret - encrypted_password = jappixmini_addon_xor(client_secret, password); + var encrypted_password = jappixmini_addon_xor(client_secret, password); encrypted_password = encodeURI(encrypted_password) callback(encrypted_password); @@ -80,10 +80,10 @@ function jappixmini_addon_decrypt_password(encrypted_password, callback) { jappixmini_addon_get_client_secret(function(client_secret){ // xor password with secret - password = jappixmini_addon_xor(client_secret, encrypted_password); + var password = jappixmini_addon_xor(client_secret, encrypted_password); // remove \0 - first_null = password.indexOf("\0") + var first_null = password.indexOf("\0") if (first_null==-1) throw "Decrypted password does not contain \\0"; password = password.substr(0, first_null); @@ -101,6 +101,8 @@ function jappixmini_manage_roster(contacts, contacts_hash, autoapprove, autosubs var xid = bareXID(from); var pstatus = presence.getStatus(); + var approve; + if (autoapprove && contacts[xid]!==undefined) { // approve known address approve = true; @@ -117,7 +119,7 @@ function jappixmini_manage_roster(contacts, contacts_hash, autoapprove, autosubs } else { // In all other cases, ask the user. - message = "Accept "+xid+" for chat?"; + var message = "Accept "+xid+" for chat?"; if (pstatus) message += "\n\nStatus:\n"+pstatus; approve = confirm(message); @@ -126,10 +128,10 @@ function jappixmini_manage_roster(contacts, contacts_hash, autoapprove, autosubs } if (approve) { - name = contacts[xid]; + var name = contacts[xid]; if (!name) name = xid; - acceptSubscribe(xid, contacts[xid]); + acceptSubscribe(xid, name); console.log("Accepted "+xid+" for chat."); } }); @@ -137,8 +139,8 @@ function jappixmini_manage_roster(contacts, contacts_hash, autoapprove, autosubs // autosubscribe if (!autosubscribe) return; - stored_hash = getPersistent("jappix-mini", "contacts-hash"); - contacts_changed = (stored_hash != contacts_hash); // stored_hash gets updated later if everything was successful + var stored_hash = getPersistent("jappix-mini", "contacts-hash"); + var contacts_changed = (stored_hash != contacts_hash); // stored_hash gets updated later if everything was successful if (!contacts_changed) return; console.log("Start autosubscribe."); @@ -152,18 +154,19 @@ function jappixmini_manage_roster(contacts, contacts_hash, autoapprove, autosubs // filter out contacts that are already in the roster $(handleXML).find('item').each(function() { - xid = $(this).attr("jid"); - name = $(this).attr("name"); - subscription = $(this).attr("subscription"); + var node = $(this); + var xid = node.attr("jid"); + var name = node.attr("name"); + var subscription = node.attr("subscription"); - // ignore accounts not in the list + // ignore accounts that are not in the list if (contacts[xid]===undefined) return; // add to Friendica group if necessary - groups = []; - $(this).find('group').each(function() { + var groups = []; + node.find('group').each(function() { var group_text = $(this).text(); - if(group_text) groups.push(group_text); + if (group_text) groups.push(group_text); }); if ($.inArray("Friendica", groups)==-1) { @@ -219,21 +222,21 @@ function jappixmini_addon_subscribe() { return; } - xid = prompt("Jabber address"); + var xid = prompt("Jabber address"); sendSubscribe(xid, "subscribe"); } function jappixmini_addon_start(server, username, proxy, bosh, encrypted, password, nickname, contacts, contacts_hash, autoapprove, autosubscribe) { - handler = function(password){ + var handler = function(password){ // check if settings have changed, reinitialize jappix mini if this is the case - settings_identifier = str_sha1(server); + var settings_identifier = str_sha1(server); settings_identifier += str_sha1(username); settings_identifier += str_sha1(proxy); settings_identifier += str_sha1(bosh); settings_identifier += str_sha1(password); settings_identifier += str_sha1(nickname); - saved_identifier = getDB("jappix-mini", "settings-identifier"); + var saved_identifier = getDB("jappix-mini", "settings-identifier"); if (saved_identifier != settings_identifier) { disconnectMini(); removeDB('jappix-mini', 'dom'); @@ -241,11 +244,11 @@ function jappixmini_addon_start(server, username, proxy, bosh, encrypted, passwo } setDB("jappix-mini", "settings-identifier", settings_identifier); - // set HOST_BOSH - if (proxy) - HOST_BOSH = proxy+"?host_bosh="+encodeURI(bosh); - else - HOST_BOSH = bosh; + // set HOST_BOSH + if (proxy) + HOST_BOSH = proxy+"?host_bosh="+encodeURI(bosh); + else + HOST_BOSH = bosh; // start jappix mini MINI_NICKNAME = nickname; @@ -253,7 +256,7 @@ function jappixmini_addon_start(server, username, proxy, bosh, encrypted, passwo launchMini(true, false, server, username, password); // increase priority over other Jabber clients - does not seem to work? - priority = 101; + var priority = 101; presenceMini(null,null,priority); jappixmini_manage_roster(contacts, contacts_hash, autoapprove, autosubscribe) From 07a1e7a2aa3a77dd2dc1fcf8d78b2c538484bb42 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Tue, 17 Apr 2012 22:45:18 +0200 Subject: [PATCH 52/96] jappixmini: log names --- jappixmini/lib.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jappixmini/lib.js b/jappixmini/lib.js index ca0130a9..1794def6 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -132,7 +132,7 @@ function jappixmini_manage_roster(contacts, contacts_hash, autoapprove, autosubs if (!name) name = xid; acceptSubscribe(xid, name); - console.log("Accepted "+xid+" for chat."); + console.log("Accepted "+xid+" ("+name+") for chat."); } }); @@ -207,7 +207,7 @@ function jappixmini_manage_roster(contacts, contacts_hash, autoapprove, autosubs item.setAttribute('name', contacts[xid]); item.appendChild(iq.buildNode('group', {'xmlns': NS_ROSTER}, "Friendica")); con.send(iq); - console.log("Added "+xid+" to roster."); + console.log("Added "+xid+" ("+contacts[xid]+") to roster."); } setPersistent("jappix-mini", "contacts-hash", contacts_hash); From efb62aec527203b1f14e199b3895fcf970b29c23 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Tue, 17 Apr 2012 23:09:26 +0200 Subject: [PATCH 53/96] jappixmini: fix names --- jappixmini/lib.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jappixmini/lib.js b/jappixmini/lib.js index 1794def6..6c47f590 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -162,17 +162,17 @@ function jappixmini_manage_roster(contacts, contacts_hash, autoapprove, autosubs // ignore accounts that are not in the list if (contacts[xid]===undefined) return; - // add to Friendica group if necessary + // add to Friendica group or change name if necessary var groups = []; node.find('group').each(function() { var group_text = $(this).text(); if (group_text) groups.push(group_text); }); - if ($.inArray("Friendica", groups)==-1) { + if ($.inArray("Friendica", groups)==-1 || name!=contacts[xid]) { groups.push("Friendica"); - sendRoster(xid, null, null, groups); - console.log("Added "+xid+" to Friendica group."); + sendRoster(xid, null, contacts[xid], groups); + console.log("Added "+xid+" to Friendica group and set name to "+contacts[xid]+"."); } // authorize if necessary From b7b302105316a5915e02d28803aab61809633b3e Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Tue, 17 Apr 2012 23:38:27 +0200 Subject: [PATCH 54/96] jappixmini: no multiple 'Friendica' groups in roster --- jappixmini/lib.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/jappixmini/lib.js b/jappixmini/lib.js index 6c47f590..deeb8b46 100644 --- a/jappixmini/lib.js +++ b/jappixmini/lib.js @@ -164,13 +164,17 @@ function jappixmini_manage_roster(contacts, contacts_hash, autoapprove, autosubs // add to Friendica group or change name if necessary var groups = []; + var group_missing = false; node.find('group').each(function() { var group_text = $(this).text(); if (group_text) groups.push(group_text); }); - - if ($.inArray("Friendica", groups)==-1 || name!=contacts[xid]) { + if ($.inArray("Friendica", groups)==-1) { + group_missing = true; groups.push("Friendica"); + } + + if (group_missing || name!=contacts[xid]) { sendRoster(xid, null, contacts[xid], groups); console.log("Added "+xid+" to Friendica group and set name to "+contacts[xid]+"."); } From 61eb1f0d187f639b623b478381973ef93d14f90d Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Wed, 18 Apr 2012 00:32:04 +0200 Subject: [PATCH 55/96] jappixmini: prepare for safe updates of the addon --- jappixmini/jappixmini.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jappixmini/jappixmini.php b/jappixmini/jappixmini.php index d09427e7..6ce7d741 100644 --- a/jappixmini/jappixmini.php +++ b/jappixmini/jappixmini.php @@ -80,7 +80,11 @@ if (!$info_text) set_config("jappixmini", "infotext", ); $bosh_proxy = get_config("jappixmini", "bosh_proxy"); -if ($bosh_proxy==="") set_config("jappixmini", "bosh_proxy", 1); +if ($bosh_proxy==="") set_config("jappixmini", "bosh_proxy", "1"); + +// set addon version so that safe updates are possible later +$addon_version = get_config("jappixmini", "version"); +if ($addon_version==="") set_config("jappixmini", "version", "1"); } From 302b2820d104de9f597804da175eccbb7c9c98b1 Mon Sep 17 00:00:00 2001 From: Leberwurscht Date: Wed, 18 Apr 2012 01:12:24 +0200 Subject: [PATCH 56/96] jappixmini: include jappix source --- .gitignore | 2 - jappixmini/jappix/AUTHORS | 54 + jappixmini/jappix/COPYING | 662 ++ jappixmini/jappix/INSTALL | 23 + jappixmini/jappix/README | 20 + jappixmini/jappix/THANKS | 27 + jappixmini/jappix/VERSION | 1 + jappixmini/jappix/css/adhoc.css | 26 + jappixmini/jappix/css/anonymous.css | 29 + jappixmini/jappix/css/archives.css | 93 + jappixmini/jappix/css/board.css | 47 + jappixmini/jappix/css/buddylist.css | 530 ++ jappixmini/jappix/css/channel.css | 545 ++ jappixmini/jappix/css/directory.css | 16 + jappixmini/jappix/css/discovery.css | 61 + jappixmini/jappix/css/favorites.css | 179 + jappixmini/jappix/css/home.css | 579 ++ jappixmini/jappix/css/ie.css | 146 + jappixmini/jappix/css/images.css | 89 + jappixmini/jappix/css/inbox.css | 202 + jappixmini/jappix/css/install.css | 285 + jappixmini/jappix/css/integratebox.css | 34 + jappixmini/jappix/css/jquery.datepicker.css | 148 + jappixmini/jappix/css/main.css | 131 + jappixmini/jappix/css/manager.css | 543 ++ jappixmini/jappix/css/me.css | 49 + jappixmini/jappix/css/mini-ie.css | 73 + jappixmini/jappix/css/mini.css | 540 ++ jappixmini/jappix/css/mobile.css | 288 + jappixmini/jappix/css/mucadmin.css | 91 + jappixmini/jappix/css/myinfos.css | 330 + jappixmini/jappix/css/options.css | 97 + jappixmini/jappix/css/others.css | 118 + jappixmini/jappix/css/pageengine.css | 601 ++ jappixmini/jappix/css/pageswitch.css | 209 + jappixmini/jappix/css/popup.css | 612 ++ jappixmini/jappix/css/privacy.css | 197 + jappixmini/jappix/css/rosterx.css | 53 + jappixmini/jappix/css/search.css | 60 + jappixmini/jappix/css/smileys.css | 196 + jappixmini/jappix/css/stats-svg.css | 71 + jappixmini/jappix/css/tools.css | 346 + jappixmini/jappix/css/userinfos.css | 100 + jappixmini/jappix/css/vcard.css | 106 + jappixmini/jappix/css/welcome.css | 170 + jappixmini/jappix/favicon.ico | Bin 0 -> 1150 bytes jappixmini/jappix/img/others/blank.gif | Bin 0 -> 43 bytes .../jappix/img/others/default-avatar.png | Bin 0 -> 1436 bytes jappixmini/jappix/img/others/lock.png | Bin 0 -> 140 bytes jappixmini/jappix/img/sprites/animate.gif | Bin 0 -> 1898 bytes jappixmini/jappix/img/sprites/animate.png | Bin 0 -> 7746 bytes jappixmini/jappix/img/sprites/background.png | Bin 0 -> 8782 bytes jappixmini/jappix/img/sprites/browsers.png | Bin 0 -> 10955 bytes jappixmini/jappix/img/sprites/buttons.png | Bin 0 -> 379 bytes jappixmini/jappix/img/sprites/home.png | Bin 0 -> 28465 bytes jappixmini/jappix/img/sprites/install.png | Bin 0 -> 9980 bytes jappixmini/jappix/img/sprites/logs.png | Bin 0 -> 2355 bytes jappixmini/jappix/img/sprites/manager.png | Bin 0 -> 21102 bytes jappixmini/jappix/img/sprites/me.png | Bin 0 -> 5100 bytes jappixmini/jappix/img/sprites/mini.gif | Bin 0 -> 3210 bytes jappixmini/jappix/img/sprites/mini.png | Bin 0 -> 4820 bytes jappixmini/jappix/img/sprites/mobile.png | Bin 0 -> 2425 bytes jappixmini/jappix/img/sprites/smileys.png | Bin 0 -> 19253 bytes jappixmini/jappix/img/sprites/talk.png | Bin 0 -> 43207 bytes jappixmini/jappix/img/sprites/welcome.png | Bin 0 -> 4356 bytes jappixmini/jappix/img/wait/wait-big.gif | Bin 0 -> 3496 bytes jappixmini/jappix/img/wait/wait-medium.png | Bin 0 -> 4129 bytes jappixmini/jappix/img/wait/wait-small.gif | Bin 0 -> 847 bytes jappixmini/jappix/js/adhoc.js | 85 + jappixmini/jappix/js/anonymous.js | 131 + jappixmini/jappix/js/archives.js | 418 + jappixmini/jappix/js/audio.js | 46 + jappixmini/jappix/js/autocompletion.js | 99 + jappixmini/jappix/js/avatar.js | 205 + jappixmini/jappix/js/base64.js | 80 + jappixmini/jappix/js/board.js | 141 + jappixmini/jappix/js/browser-detect.js | 124 + jappixmini/jappix/js/bubble.js | 59 + jappixmini/jappix/js/caps.js | 349 + jappixmini/jappix/js/chat.js | 297 + jappixmini/jappix/js/chatstate.js | 174 + jappixmini/jappix/js/common.js | 311 + jappixmini/jappix/js/connection.js | 526 ++ jappixmini/jappix/js/constants.js | 211 + jappixmini/jappix/js/dataform.js | 921 +++ jappixmini/jappix/js/datastore.js | 209 + jappixmini/jappix/js/date.js | 212 + jappixmini/jappix/js/directory.js | 87 + jappixmini/jappix/js/discovery.js | 169 + jappixmini/jappix/js/error.js | 139 + jappixmini/jappix/js/favorites.js | 537 ++ jappixmini/jappix/js/features.js | 213 + jappixmini/jappix/js/filter.js | 189 + jappixmini/jappix/js/groupchat.js | 283 + jappixmini/jappix/js/home.js | 371 + jappixmini/jappix/js/httpauth.js | 41 + jappixmini/jappix/js/inbox.js | 694 ++ jappixmini/jappix/js/integratebox.js | 340 + jappixmini/jappix/js/interface.js | 386 + jappixmini/jappix/js/iq.js | 193 + jappixmini/jappix/js/jquery.datepicker.js | 896 ++ jappixmini/jappix/js/jquery.form.js | 785 ++ jappixmini/jappix/js/jquery.js | 7179 +++++++++++++++++ jappixmini/jappix/js/jquery.placeholder.js | 116 + jappixmini/jappix/js/jquery.textrange.js | 14 + jappixmini/jappix/js/jquery.timers.js | 138 + jappixmini/jappix/js/jsjac.js | 4288 ++++++++++ jappixmini/jappix/js/jxhr.js | 116 + jappixmini/jappix/js/links.js | 38 + jappixmini/jappix/js/me.js | 62 + jappixmini/jappix/js/message.js | 900 +++ jappixmini/jappix/js/microblog.js | 1447 ++++ jappixmini/jappix/js/mini.js | 1623 ++++ jappixmini/jappix/js/mobile.js | 592 ++ jappixmini/jappix/js/mucadmin.js | 373 + jappixmini/jappix/js/music.js | 259 + jappixmini/jappix/js/name.js | 131 + jappixmini/jappix/js/notification.js | 422 + jappixmini/jappix/js/oob.js | 184 + jappixmini/jappix/js/options.js | 643 ++ jappixmini/jappix/js/pep.js | 827 ++ jappixmini/jappix/js/popup.js | 42 + jappixmini/jappix/js/presence.js | 1075 +++ jappixmini/jappix/js/privacy.js | 963 +++ jappixmini/jappix/js/receipts.js | 94 + jappixmini/jappix/js/roster.js | 1122 +++ jappixmini/jappix/js/rosterx.js | 187 + jappixmini/jappix/js/search.js | 288 + jappixmini/jappix/js/smileys.js | 113 + jappixmini/jappix/js/storage.js | 134 + jappixmini/jappix/js/talk.js | 263 + jappixmini/jappix/js/tooltip.js | 268 + jappixmini/jappix/js/userinfos.js | 426 + jappixmini/jappix/js/utilities.js | 437 + jappixmini/jappix/js/vcard.js | 630 ++ jappixmini/jappix/js/welcome.js | 299 + jappixmini/jappix/js/xmpplinks.js | 78 + jappixmini/jappix/lang/ar/LC_MESSAGES/main.mo | Bin 0 -> 13245 bytes jappixmini/jappix/lang/ar/LC_MESSAGES/main.po | 1870 +++++ jappixmini/jappix/lang/bg/LC_MESSAGES/main.mo | Bin 0 -> 64778 bytes jappixmini/jappix/lang/bg/LC_MESSAGES/main.po | 1870 +++++ jappixmini/jappix/lang/cs/LC_MESSAGES/main.mo | Bin 0 -> 49063 bytes jappixmini/jappix/lang/cs/LC_MESSAGES/main.po | 1870 +++++ jappixmini/jappix/lang/de/LC_MESSAGES/main.mo | Bin 0 -> 49073 bytes jappixmini/jappix/lang/de/LC_MESSAGES/main.po | 1870 +++++ .../jappix/lang/en/LC_MESSAGES/main.pot | 1856 +++++ jappixmini/jappix/lang/eo/LC_MESSAGES/main.mo | Bin 0 -> 48077 bytes jappixmini/jappix/lang/eo/LC_MESSAGES/main.po | 1870 +++++ jappixmini/jappix/lang/es/LC_MESSAGES/main.mo | Bin 0 -> 43885 bytes jappixmini/jappix/lang/es/LC_MESSAGES/main.po | 1870 +++++ jappixmini/jappix/lang/et/LC_MESSAGES/main.mo | Bin 0 -> 47624 bytes jappixmini/jappix/lang/et/LC_MESSAGES/main.po | 1870 +++++ jappixmini/jappix/lang/fa/LC_MESSAGES/main.mo | Bin 0 -> 26308 bytes jappixmini/jappix/lang/fa/LC_MESSAGES/main.po | 1870 +++++ jappixmini/jappix/lang/fr/LC_MESSAGES/main.mo | Bin 0 -> 52792 bytes jappixmini/jappix/lang/fr/LC_MESSAGES/main.po | 1870 +++++ jappixmini/jappix/lang/hu/LC_MESSAGES/main.mo | Bin 0 -> 50183 bytes jappixmini/jappix/lang/hu/LC_MESSAGES/main.po | 1870 +++++ jappixmini/jappix/lang/id/LC_MESSAGES/main.mo | Bin 0 -> 48941 bytes jappixmini/jappix/lang/id/LC_MESSAGES/main.po | 1870 +++++ jappixmini/jappix/lang/it/LC_MESSAGES/main.mo | Bin 0 -> 44893 bytes jappixmini/jappix/lang/it/LC_MESSAGES/main.po | 1870 +++++ jappixmini/jappix/lang/ja/LC_MESSAGES/main.mo | Bin 0 -> 52928 bytes jappixmini/jappix/lang/ja/LC_MESSAGES/main.po | 1870 +++++ jappixmini/jappix/lang/nl/LC_MESSAGES/main.mo | Bin 0 -> 9342 bytes jappixmini/jappix/lang/nl/LC_MESSAGES/main.po | 1870 +++++ jappixmini/jappix/lang/oc/LC_MESSAGES/main.mo | Bin 0 -> 13730 bytes jappixmini/jappix/lang/oc/LC_MESSAGES/main.po | 1870 +++++ jappixmini/jappix/lang/pl/LC_MESSAGES/main.mo | Bin 0 -> 41024 bytes jappixmini/jappix/lang/pl/LC_MESSAGES/main.po | 1870 +++++ .../jappix/lang/pt-BR/LC_MESSAGES/main.mo | Bin 0 -> 48169 bytes .../jappix/lang/pt-BR/LC_MESSAGES/main.po | 1870 +++++ jappixmini/jappix/lang/ru/LC_MESSAGES/main.mo | Bin 0 -> 60469 bytes jappixmini/jappix/lang/ru/LC_MESSAGES/main.po | 1870 +++++ jappixmini/jappix/lang/sk/LC_MESSAGES/main.mo | Bin 0 -> 43057 bytes jappixmini/jappix/lang/sk/LC_MESSAGES/main.po | 1870 +++++ jappixmini/jappix/lang/sv/LC_MESSAGES/main.mo | Bin 0 -> 29178 bytes jappixmini/jappix/lang/sv/LC_MESSAGES/main.po | 1870 +++++ jappixmini/jappix/lang/uk/LC_MESSAGES/main.mo | Bin 0 -> 19238 bytes jappixmini/jappix/lang/uk/LC_MESSAGES/main.po | 1870 +++++ jappixmini/jappix/lang/zh/LC_MESSAGES/main.mo | Bin 0 -> 35976 bytes jappixmini/jappix/lang/zh/LC_MESSAGES/main.po | 1870 +++++ jappixmini/jappix/php/avatar-upload.php | 118 + jappixmini/jappix/php/bosh.php | 167 + jappixmini/jappix/php/desktop.php | 181 + jappixmini/jappix/php/download-chat.php | 53 + jappixmini/jappix/php/drawsvgchart.php | 495 ++ jappixmini/jappix/php/file-share.php | 122 + jappixmini/jappix/php/form-hosts.php | 49 + jappixmini/jappix/php/form-main.php | 125 + jappixmini/jappix/php/form-users.php | 30 + jappixmini/jappix/php/functions-get.php | 280 + jappixmini/jappix/php/functions-manager.php | 769 ++ jappixmini/jappix/php/functions.php | 1427 ++++ jappixmini/jappix/php/generate-chat.php | 235 + jappixmini/jappix/php/geolocation.php | 43 + jappixmini/jappix/php/get-store.php | 37 + jappixmini/jappix/php/get.php | 340 + jappixmini/jappix/php/gettext.php | 949 +++ jappixmini/jappix/php/install.php | 289 + jappixmini/jappix/php/jsmin.php | 375 + jappixmini/jappix/php/manager.php | 839 ++ jappixmini/jappix/php/mobile-detect.php | 100 + jappixmini/jappix/php/mobile.php | 73 + jappixmini/jappix/php/music-search.php | 103 + jappixmini/jappix/php/post-design.php | 215 + jappixmini/jappix/php/post-hosts.php | 95 + jappixmini/jappix/php/post-main.php | 130 + jappixmini/jappix/php/post-store.php | 100 + jappixmini/jappix/php/post-users.php | 48 + jappixmini/jappix/php/read-design.php | 125 + jappixmini/jappix/php/read-hosts.php | 84 + jappixmini/jappix/php/read-main.php | 77 + jappixmini/jappix/php/send.php | 130 + jappixmini/jappix/php/static.php | 40 + jappixmini/jappix/php/stats-svg.php | 106 + jappixmini/jappix/php/store-tree.php | 51 + jappixmini/jappix/php/upload.php | 40 + jappixmini/jappix/php/vars-design.php | 49 + jappixmini/jappix/php/vars-hosts.php | 32 + jappixmini/jappix/php/vars-main.php | 37 + jappixmini/jappix/php/vars-store.php | 29 + jappixmini/jappix/snd/new-chat.oga | Bin 0 -> 6472 bytes jappixmini/jappix/snd/notification.oga | Bin 0 -> 3710 bytes jappixmini/jappix/snd/receive-message.oga | Bin 0 -> 5271 bytes jappixmini/jappix/xml/anonymous.xml | 5 + jappixmini/jappix/xml/desktop.xml | 5 + jappixmini/jappix/xml/install.xml | 5 + jappixmini/jappix/xml/manager.xml | 5 + jappixmini/jappix/xml/mini.xml | 5 + jappixmini/jappix/xml/mobile.xml | 5 + 231 files changed, 96082 insertions(+), 2 deletions(-) delete mode 100644 .gitignore create mode 100644 jappixmini/jappix/AUTHORS create mode 100644 jappixmini/jappix/COPYING create mode 100644 jappixmini/jappix/INSTALL create mode 100644 jappixmini/jappix/README create mode 100644 jappixmini/jappix/THANKS create mode 100644 jappixmini/jappix/VERSION create mode 100644 jappixmini/jappix/css/adhoc.css create mode 100644 jappixmini/jappix/css/anonymous.css create mode 100644 jappixmini/jappix/css/archives.css create mode 100644 jappixmini/jappix/css/board.css create mode 100644 jappixmini/jappix/css/buddylist.css create mode 100644 jappixmini/jappix/css/channel.css create mode 100644 jappixmini/jappix/css/directory.css create mode 100644 jappixmini/jappix/css/discovery.css create mode 100644 jappixmini/jappix/css/favorites.css create mode 100644 jappixmini/jappix/css/home.css create mode 100644 jappixmini/jappix/css/ie.css create mode 100644 jappixmini/jappix/css/images.css create mode 100644 jappixmini/jappix/css/inbox.css create mode 100644 jappixmini/jappix/css/install.css create mode 100644 jappixmini/jappix/css/integratebox.css create mode 100644 jappixmini/jappix/css/jquery.datepicker.css create mode 100644 jappixmini/jappix/css/main.css create mode 100644 jappixmini/jappix/css/manager.css create mode 100644 jappixmini/jappix/css/me.css create mode 100644 jappixmini/jappix/css/mini-ie.css create mode 100644 jappixmini/jappix/css/mini.css create mode 100644 jappixmini/jappix/css/mobile.css create mode 100644 jappixmini/jappix/css/mucadmin.css create mode 100644 jappixmini/jappix/css/myinfos.css create mode 100644 jappixmini/jappix/css/options.css create mode 100644 jappixmini/jappix/css/others.css create mode 100644 jappixmini/jappix/css/pageengine.css create mode 100644 jappixmini/jappix/css/pageswitch.css create mode 100644 jappixmini/jappix/css/popup.css create mode 100644 jappixmini/jappix/css/privacy.css create mode 100644 jappixmini/jappix/css/rosterx.css create mode 100644 jappixmini/jappix/css/search.css create mode 100644 jappixmini/jappix/css/smileys.css create mode 100644 jappixmini/jappix/css/stats-svg.css create mode 100644 jappixmini/jappix/css/tools.css create mode 100644 jappixmini/jappix/css/userinfos.css create mode 100644 jappixmini/jappix/css/vcard.css create mode 100644 jappixmini/jappix/css/welcome.css create mode 100644 jappixmini/jappix/favicon.ico create mode 100644 jappixmini/jappix/img/others/blank.gif create mode 100644 jappixmini/jappix/img/others/default-avatar.png create mode 100644 jappixmini/jappix/img/others/lock.png create mode 100644 jappixmini/jappix/img/sprites/animate.gif create mode 100644 jappixmini/jappix/img/sprites/animate.png create mode 100644 jappixmini/jappix/img/sprites/background.png create mode 100644 jappixmini/jappix/img/sprites/browsers.png create mode 100644 jappixmini/jappix/img/sprites/buttons.png create mode 100644 jappixmini/jappix/img/sprites/home.png create mode 100644 jappixmini/jappix/img/sprites/install.png create mode 100644 jappixmini/jappix/img/sprites/logs.png create mode 100644 jappixmini/jappix/img/sprites/manager.png create mode 100644 jappixmini/jappix/img/sprites/me.png create mode 100644 jappixmini/jappix/img/sprites/mini.gif create mode 100644 jappixmini/jappix/img/sprites/mini.png create mode 100644 jappixmini/jappix/img/sprites/mobile.png create mode 100644 jappixmini/jappix/img/sprites/smileys.png create mode 100644 jappixmini/jappix/img/sprites/talk.png create mode 100644 jappixmini/jappix/img/sprites/welcome.png create mode 100644 jappixmini/jappix/img/wait/wait-big.gif create mode 100644 jappixmini/jappix/img/wait/wait-medium.png create mode 100644 jappixmini/jappix/img/wait/wait-small.gif create mode 100644 jappixmini/jappix/js/adhoc.js create mode 100644 jappixmini/jappix/js/anonymous.js create mode 100644 jappixmini/jappix/js/archives.js create mode 100644 jappixmini/jappix/js/audio.js create mode 100644 jappixmini/jappix/js/autocompletion.js create mode 100644 jappixmini/jappix/js/avatar.js create mode 100644 jappixmini/jappix/js/base64.js create mode 100644 jappixmini/jappix/js/board.js create mode 100644 jappixmini/jappix/js/browser-detect.js create mode 100644 jappixmini/jappix/js/bubble.js create mode 100644 jappixmini/jappix/js/caps.js create mode 100644 jappixmini/jappix/js/chat.js create mode 100644 jappixmini/jappix/js/chatstate.js create mode 100644 jappixmini/jappix/js/common.js create mode 100644 jappixmini/jappix/js/connection.js create mode 100644 jappixmini/jappix/js/constants.js create mode 100644 jappixmini/jappix/js/dataform.js create mode 100644 jappixmini/jappix/js/datastore.js create mode 100644 jappixmini/jappix/js/date.js create mode 100644 jappixmini/jappix/js/directory.js create mode 100644 jappixmini/jappix/js/discovery.js create mode 100644 jappixmini/jappix/js/error.js create mode 100644 jappixmini/jappix/js/favorites.js create mode 100644 jappixmini/jappix/js/features.js create mode 100644 jappixmini/jappix/js/filter.js create mode 100644 jappixmini/jappix/js/groupchat.js create mode 100644 jappixmini/jappix/js/home.js create mode 100644 jappixmini/jappix/js/httpauth.js create mode 100644 jappixmini/jappix/js/inbox.js create mode 100644 jappixmini/jappix/js/integratebox.js create mode 100644 jappixmini/jappix/js/interface.js create mode 100644 jappixmini/jappix/js/iq.js create mode 100644 jappixmini/jappix/js/jquery.datepicker.js create mode 100644 jappixmini/jappix/js/jquery.form.js create mode 100644 jappixmini/jappix/js/jquery.js create mode 100644 jappixmini/jappix/js/jquery.placeholder.js create mode 100644 jappixmini/jappix/js/jquery.textrange.js create mode 100644 jappixmini/jappix/js/jquery.timers.js create mode 100644 jappixmini/jappix/js/jsjac.js create mode 100644 jappixmini/jappix/js/jxhr.js create mode 100644 jappixmini/jappix/js/links.js create mode 100644 jappixmini/jappix/js/me.js create mode 100644 jappixmini/jappix/js/message.js create mode 100644 jappixmini/jappix/js/microblog.js create mode 100644 jappixmini/jappix/js/mini.js create mode 100644 jappixmini/jappix/js/mobile.js create mode 100644 jappixmini/jappix/js/mucadmin.js create mode 100644 jappixmini/jappix/js/music.js create mode 100644 jappixmini/jappix/js/name.js create mode 100644 jappixmini/jappix/js/notification.js create mode 100644 jappixmini/jappix/js/oob.js create mode 100644 jappixmini/jappix/js/options.js create mode 100644 jappixmini/jappix/js/pep.js create mode 100644 jappixmini/jappix/js/popup.js create mode 100644 jappixmini/jappix/js/presence.js create mode 100644 jappixmini/jappix/js/privacy.js create mode 100644 jappixmini/jappix/js/receipts.js create mode 100644 jappixmini/jappix/js/roster.js create mode 100644 jappixmini/jappix/js/rosterx.js create mode 100644 jappixmini/jappix/js/search.js create mode 100644 jappixmini/jappix/js/smileys.js create mode 100644 jappixmini/jappix/js/storage.js create mode 100644 jappixmini/jappix/js/talk.js create mode 100644 jappixmini/jappix/js/tooltip.js create mode 100644 jappixmini/jappix/js/userinfos.js create mode 100644 jappixmini/jappix/js/utilities.js create mode 100644 jappixmini/jappix/js/vcard.js create mode 100644 jappixmini/jappix/js/welcome.js create mode 100644 jappixmini/jappix/js/xmpplinks.js create mode 100644 jappixmini/jappix/lang/ar/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/ar/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/bg/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/bg/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/cs/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/cs/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/de/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/de/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/en/LC_MESSAGES/main.pot create mode 100644 jappixmini/jappix/lang/eo/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/eo/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/es/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/es/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/et/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/et/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/fa/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/fa/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/fr/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/fr/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/hu/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/hu/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/id/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/id/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/it/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/it/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/ja/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/ja/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/nl/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/nl/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/oc/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/oc/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/pl/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/pl/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/pt-BR/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/pt-BR/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/ru/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/ru/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/sk/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/sk/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/sv/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/sv/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/uk/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/uk/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/lang/zh/LC_MESSAGES/main.mo create mode 100644 jappixmini/jappix/lang/zh/LC_MESSAGES/main.po create mode 100644 jappixmini/jappix/php/avatar-upload.php create mode 100644 jappixmini/jappix/php/bosh.php create mode 100644 jappixmini/jappix/php/desktop.php create mode 100644 jappixmini/jappix/php/download-chat.php create mode 100644 jappixmini/jappix/php/drawsvgchart.php create mode 100644 jappixmini/jappix/php/file-share.php create mode 100644 jappixmini/jappix/php/form-hosts.php create mode 100644 jappixmini/jappix/php/form-main.php create mode 100644 jappixmini/jappix/php/form-users.php create mode 100644 jappixmini/jappix/php/functions-get.php create mode 100644 jappixmini/jappix/php/functions-manager.php create mode 100644 jappixmini/jappix/php/functions.php create mode 100644 jappixmini/jappix/php/generate-chat.php create mode 100644 jappixmini/jappix/php/geolocation.php create mode 100644 jappixmini/jappix/php/get-store.php create mode 100644 jappixmini/jappix/php/get.php create mode 100644 jappixmini/jappix/php/gettext.php create mode 100644 jappixmini/jappix/php/install.php create mode 100644 jappixmini/jappix/php/jsmin.php create mode 100644 jappixmini/jappix/php/manager.php create mode 100644 jappixmini/jappix/php/mobile-detect.php create mode 100644 jappixmini/jappix/php/mobile.php create mode 100644 jappixmini/jappix/php/music-search.php create mode 100644 jappixmini/jappix/php/post-design.php create mode 100644 jappixmini/jappix/php/post-hosts.php create mode 100644 jappixmini/jappix/php/post-main.php create mode 100644 jappixmini/jappix/php/post-store.php create mode 100644 jappixmini/jappix/php/post-users.php create mode 100644 jappixmini/jappix/php/read-design.php create mode 100644 jappixmini/jappix/php/read-hosts.php create mode 100644 jappixmini/jappix/php/read-main.php create mode 100644 jappixmini/jappix/php/send.php create mode 100644 jappixmini/jappix/php/static.php create mode 100644 jappixmini/jappix/php/stats-svg.php create mode 100644 jappixmini/jappix/php/store-tree.php create mode 100644 jappixmini/jappix/php/upload.php create mode 100644 jappixmini/jappix/php/vars-design.php create mode 100644 jappixmini/jappix/php/vars-hosts.php create mode 100644 jappixmini/jappix/php/vars-main.php create mode 100644 jappixmini/jappix/php/vars-store.php create mode 100644 jappixmini/jappix/snd/new-chat.oga create mode 100644 jappixmini/jappix/snd/notification.oga create mode 100644 jappixmini/jappix/snd/receive-message.oga create mode 100644 jappixmini/jappix/xml/anonymous.xml create mode 100644 jappixmini/jappix/xml/desktop.xml create mode 100644 jappixmini/jappix/xml/install.xml create mode 100644 jappixmini/jappix/xml/manager.xml create mode 100644 jappixmini/jappix/xml/mini.xml create mode 100644 jappixmini/jappix/xml/mobile.xml diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 77d48406..00000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -jappixmini/jappix/ -jappixmini/jappix.zip diff --git a/jappixmini/jappix/AUTHORS b/jappixmini/jappix/AUTHORS new file mode 100644 index 00000000..0086eb6a --- /dev/null +++ b/jappixmini/jappix/AUTHORS @@ -0,0 +1,54 @@ +Jappix - An open social platform +These are the authors of Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 15/01/12 + +------------------------------------------------- + +Here are the Jappix contributors, who coded or translated the application (Codingteam.net nicknames): + +# DEVELOPERS + - am0ur3ux + - LinkMauve + - Maranda + - Mathieui + - Olivier + - sim6 + - Vanaryon + +# TRANSLATORS + - allan + - Arsimael + - Belzeneph + - Catdarko + - Cerritus + - chunzu + - ebraminio + - Finkregh + - hamano + - JanCBorchardt + - jarda + - joeka + - kr2ysiek + - krohn + - Lenwe + - LinkMauve + - Liverbool + - lwj + - m1st + - Maime + - Maranda + - mbajur + - mentalo + - mkwm + - Otourly + - pocamon + - quimi + - sahwar + - Vanaryon + - vitalyster + - Zash diff --git a/jappixmini/jappix/COPYING b/jappixmini/jappix/COPYING new file mode 100644 index 00000000..84ae389f --- /dev/null +++ b/jappixmini/jappix/COPYING @@ -0,0 +1,662 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license +for software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are +designed to take away your freedom to share and change the works. By +contrast, our General Public Licenses are intended to guarantee your +freedom to share and change all versions of a program--to make sure it +remains free software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public +License. + + "Copyright" also means copyright-like laws that apply to other kinds +of works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further restriction, +you may remove that term. If a license document contains a further +restriction but permits relicensing or conveying under this License, you +may add to a covered work material governed by the terms of that license +document, provided that the further restriction does not survive such +relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have permission +to link or combine any covered work with a work licensed under version 3 +of the GNU General Public License into a single combined work, and to +convey the resulting work. The terms of this License will continue to +apply to the part which is the covered work, but the work with which it is +combined will remain governed by version 3 of the GNU General Public +License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may differ +in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero +General Public License "or any later version" applies to it, you have +the option of following the terms and conditions either of that +numbered version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number +of the GNU Affero General Public License, you may choose any version +ever published by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that +proxy's public statement of acceptance of a version permanently +authorizes you to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. \ No newline at end of file diff --git a/jappixmini/jappix/INSTALL b/jappixmini/jappix/INSTALL new file mode 100644 index 00000000..652c1be7 --- /dev/null +++ b/jappixmini/jappix/INSTALL @@ -0,0 +1,23 @@ +Jappix - An open social platform +These are the installation instructions for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 15/09/10 + +------------------------------------------------- + +It's very simple to install Jappix on your webserver, you just have to follow these things: + +# INSTALLATION + - The HTTP server : http://codingteam.net/project/jappix/doc/HttpServer + - The XMPP server : http://codingteam.net/project/jappix/doc/XmppServer + - The BOSH server : http://codingteam.net/project/jappix/doc/BoshServer + - The Jappix app. : http://codingteam.net/project/jappix/doc/JappixApp + +# MORE + - The whole documentation is available at : http://codingteam.net/project/jappix/doc + +Now, you can use Jappix. Happy socializing! diff --git a/jappixmini/jappix/README b/jappixmini/jappix/README new file mode 100644 index 00000000..49cbf974 --- /dev/null +++ b/jappixmini/jappix/README @@ -0,0 +1,20 @@ +Jappix - An open social platform +This is the readme file for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 18/02/11 + +------------------------------------------------- + +Please refer to the installation instructions that are located in the INSTALL file to process the Jappix installation. + +The Jappix Project official service: https://www.jappix.com/ +The Jappix Project website: https://project.jappix.com/ +The Jappix Project panel: http://codingteam.net/project/jappix + +Jappix is released under the terms of the AGPL license. See COPYING for details. + +Have fun with Jappix! diff --git a/jappixmini/jappix/THANKS b/jappixmini/jappix/THANKS new file mode 100644 index 00000000..a5b12227 --- /dev/null +++ b/jappixmini/jappix/THANKS @@ -0,0 +1,27 @@ +Jappix - An open social platform +These are the special thanks for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 16/02/11 + +------------------------------------------------- + +We would like to thanks the authors of these tools, coming from other projects: + +# PROJECTS + - Base64 http://rumkin.com + - DrawSVGChart http://codingteam.net/project/codingteam + - idzXHR http://www.iadvize.com/plugin_strophe_xmpp.html + - JSJaC http://blog.jwchat.org/jsjac/ + - jQuery http://jquery.com/ + - jQuery Form http://jquery.malsup.com/form/ + - jQuery Timers http://plugins.jquery.com/project/timers + - jXHR http://mulletxhr.com/ + - Mobile Detect http://code.google.com/p/php-mobile-detect/ + - JSMin http://github.com/rgrove/jsmin-php/ + - PHP-gettext https://launchpad.net/php-gettext + - Silk icons http://www.famfamfam.com/lab/icons/silk/ + - Smileys http://www.gajim.org/ diff --git a/jappixmini/jappix/VERSION b/jappixmini/jappix/VERSION new file mode 100644 index 00000000..76892598 --- /dev/null +++ b/jappixmini/jappix/VERSION @@ -0,0 +1 @@ +Spaco [0.9] diff --git a/jappixmini/jappix/css/adhoc.css b/jappixmini/jappix/css/adhoc.css new file mode 100644 index 00000000..68d0383f --- /dev/null +++ b/jappixmini/jappix/css/adhoc.css @@ -0,0 +1,26 @@ +/* + +Jappix - An open social platform +This is the Ad-Hoc CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 20/12/10 + +*/ + +#adhoc .content { + padding: 10px 0 10px 0; +} + +#adhoc .adhoc-head { + background-color: #f1f6fd; + border: 1px #9dc4fc solid; + width: 598px; + height: 18px; + font-size: 0.9em; + margin: 0 10px 12px 10px; + padding: 6px 10px; +} diff --git a/jappixmini/jappix/css/anonymous.css b/jappixmini/jappix/css/anonymous.css new file mode 100644 index 00000000..f61a79c6 --- /dev/null +++ b/jappixmini/jappix/css/anonymous.css @@ -0,0 +1,29 @@ +/* + +Jappix - An open social platform +This is the anonymous mode CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 06/11/10 + +*/ + +#top-content { + min-width: 500px !important; +} + +#main-content { + min-width: 490px !important; + min-height: 450px !important; +} + +#left-content { + display: none; +} + +#right-content { + left: 0; +} diff --git a/jappixmini/jappix/css/archives.css b/jappixmini/jappix/css/archives.css new file mode 100644 index 00000000..5e60a8be --- /dev/null +++ b/jappixmini/jappix/css/archives.css @@ -0,0 +1,93 @@ +/* + +Jappix - An open social platform +This is the archives CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 19/12/10 + +*/ + +#archives .content { + padding: 10px 0 10px 0; +} + +#archives .filter { + background-color: #e9f1fd; + border-right: 1px solid #9dc4fc; + width: 180px; + padding: 12px; + position: absolute; + top: 0; + left: 0; + bottom: 0; + float: left; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + -moz-border-radius-topleft: 4px; + -moz-border-radius-bottomleft: 4px; + -webkit-border-top-left-radius: 4px; + -webkit-border-bottom-left-radius: 4px; +} + +#archives .filter .friend { + margin-bottom: 12px; +} + +#archives .filter .friend { + height: 210px; + width: 180px; + float: none; +} + +#archives .current, +#archives .logs { + position: absolute; + bottom: 0; + right: 0; + left: 204px; +} + +#archives .current { + background-color: #e4eef9; + border-bottom: 1px solid #9dc4fc; + font-size: 0.9em; + height: 16px; + padding: 6px; + top: 0; +} + +#archives .current span { + height: 16px; + overflow: hidden; +} + +#archives .current .name { + max-width: 160px; + font-weight: bold; + float: left; +} + +#archives .current .time { + color: #47646a; + font-size: 0.95em; + float: right; +} + +#archives .logs { + color: black; + font-size: 0.9em; + overflow: auto; + padding: 8px 10px 0; + float: left; + position: absolute; + top: 29px; +} + +#archives .logs a { + color: black; + text-decoration: underline; +} diff --git a/jappixmini/jappix/css/board.css b/jappixmini/jappix/css/board.css new file mode 100644 index 00000000..91e6dcf9 --- /dev/null +++ b/jappixmini/jappix/css/board.css @@ -0,0 +1,47 @@ +/* + +Jappix - An open social platform +This is the board CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 26/08/11 + +*/ + +#board .one-board { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + height: 18px; + z-index: 10000; + font-size: 0.92em; + padding: 6px 8px; + box-shadow: 0 0 8px #5c5c5c; + -moz-box-shadow: 0 0 8px #5c5c5c; + -webkit-box-shadow: 0 0 8px #5c5c5c; +} + +#board .one-board:hover { + cursor: pointer; +} + +#board .one-board.visible { + display: block; +} + +#board .one-board.error { + background-color: rgb(241,160,160); + background-color: rgba(241,160,160,0.9); + color: #420c0c; +} + +#board .one-board.info { + background-color: rgb(248,246,186); + background-color: rgba(248,246,186,0.9); + color: #2f2a02; +} diff --git a/jappixmini/jappix/css/buddylist.css b/jappixmini/jappix/css/buddylist.css new file mode 100644 index 00000000..3bfa70cb --- /dev/null +++ b/jappixmini/jappix/css/buddylist.css @@ -0,0 +1,530 @@ +/* + +Jappix - An open social platform +This is the buddy-list CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 31/08/11 + +*/ + +#buddy-list { + background-color: rgb(20,20,20); + background-color: rgba(20,20,20,0.85); + color: #919191; + padding: 15px 6px 4px 6px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0 0 6px #5c5c5c; + -moz-box-shadow: 0 0 6px #5c5c5c; + -webkit-box-shadow: 0 0 6px #5c5c5c; +} + +#buddy-list .content { + background: #e8f1f3; + background: -moz-linear-gradient(top, #e8f1f3, #e4edef); + background: -webkit-gradient(linear, left top, left bottom, from(#e8f1f3), to(#e4edef)); + color: #666666; + height: 207px; + padding: 4px 4px 0 4px; + overflow-x: hidden; + overflow-y: auto; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + -moz-border-radius-topleft: 3px; + -moz-border-radius-topright: 3px; + -webkit-border-top-left-radius: 3px; + -webkit-border-top-right-radius: 3px; +} + +#buddy-list .one-group { + margin-bottom: 10px; +} + +#buddy-list .one-group a.group { + color: #202c2f; + font-size: 0.8em; + margin: 3px 6px; + padding-left: 12px; + max-height: 15px; + text-decoration: none; + overflow: hidden; + display: block; +} + +#buddy-list .one-group a.group.plus { + background-position: -4px -1143px; +} + +#buddy-list .one-group a.group.minus { + background-position: -4px -1162px; +} + +#buddy-list .one-group a.group:hover { + cursor: pointer; +} + +#buddy-list .hidden-buddy, +#buddy-list .foot-edit-finish, +.buddy-conf-more-display-available { + display: none; +} + +#buddy-list .buddy { + width: 100%; + height: 50px; + margin-bottom: 4px; +} + +#buddy-list .buddy-click { + background: #d9e7ea; + width: 100%; + height: 100%; + overflow: hidden; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; +} + +#buddy-list .buddy-click:hover, +#buddy-list .buddy-click:focus { + background: #cedee1; + cursor: pointer; +} + +#buddy-list .buddy-click:active { + background: #c3d3d7; +} + +#buddy-list .gateway { + height: 27px; +} + +#buddy-list .gateway .name { + margin-left: 0; +} + +#buddy-list .gateway .buddy-presence { + float: left; + overflow: hidden; + width: 0; + margin: 0 4px; +} + +#buddy-list .avatar-container { + float: left; + text-align: center; + margin: 3px; + width: 46px; + height: 46px; +} + +#buddy-list .avatar { + max-width: 44px; + max-height: 44px; +} + +#buddy-list .name { + margin: 4px 3px 5px 56px; +} + +#buddy-list .buddy-name { + height: 17px; + font-weight: bold; + font-size: 0.8em; + color: #264249; + margin: 5px 0 5px 2px; + overflow: hidden; +} + +#buddy-list .buddy.blocked p.buddy-name { + text-decoration: line-through; +} + +#buddy-list .buddy-presence { + height: 14px; + font-size: 0.7em; + color: #3a585e; + padding: 2px 0 0 16px; + margin-top: -3px; +} + +#buddy-list .unavailable, +#page-switch .unavailable, +#page-engine p.bc-infos span.show.unavailable { + background-position: 0 -153px; +} + +#buddy-list .available, +#page-engine p.bc-infos span.show.available, +#page-engine .list .available, +#page-engine .list .chat, +#page-switch .available, +#my-infos .f-presence a[data-value=available] span { + background-position: 0 -169px; +} + +#buddy-list .away, +#page-engine p.bc-infos span.show.away, +#page-engine .list .away, +#page-switch .away, +#my-infos .f-presence a[data-value=away] span { + background-position: 0 -185px; +} + +#buddy-list .busy, +#page-engine p.bc-infos span.show.busy, +#page-engine .list .xa, +#page-engine .list .dnd, +#page-switch .busy, +#my-infos .f-presence a[data-value=xa] span { + background-position: 0 -201px; +} + +#buddy-list .error, +#page-switch .error, +#page-engine p.bc-infos span.show.error { + background-position: 0 -217px; +} + +#buddy-list .buddy-infos { + position: absolute; + z-index: 100; + width: 337px; + color: white; + font-size: 0.8em; +} + +.buddy-infos-subarrow { + background-position: 0 -241px; + opacity: 0.8; + width: 9px; + height: 20px; + margin-top: 12px; + float: left; +} + +.buddy-infos-subitem { + background-color: rgb(0,0,0); + background-color: rgba(0,0,0,0.8); + padding: 8px 10px; + width: 308px; + text-shadow: 0 1px 1px black; + float: left; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; +} + +.manage-infos p.bm-authorize, +#rosterx .oneresult span.action.add { + background-position: 0 -1181px; +} + +.manage-infos p.bm-remove, +#rosterx .oneresult span.action.delete, +#attach div.one-file a.remove { + background-position: 0 -1200px; +} + +.manage-infos p.bm-remove { + margin-bottom: 18px; +} + +.manage-infos p.bm-rename { + background-position: 0 -1216px; +} + +.manage-infos p.bm-group { + background-position: 0 -1241px; +} + +.manage-infos div.bm-choose { + max-height: 95px; + margin: 0 0 8px 102px; + overflow: auto; +} + +.manage-infos div.bm-choose label { + float: left; + clear: both; + margin-bottom: 1px; +} + +.manage-infos div.bm-choose input { + float: left; +} + +.manage-infos div.bm-choose input[type=checkbox] { + margin: 0 6px 0 0; +} + +.manage-infos div.bm-choose div { + clear: both; +} + +.manage-infos p.bm-rename, +.manage-infos p.bm-group { + height: 26px; +} + +.manage-infos p.bm-rename label, +.manage-infos p.bm-group label { + width: 80px; + padding-top: 3px; + float: left; +} + +.manage-infos p.bm-rename input, +.manage-infos p.bm-group input { + float: left; + width: 155px; +} + +.manage-infos a.save { + float: right; + margin: 4px; +} + +.buddy-infos-subitem p { + margin: 6px 0; + padding-left: 22px; + height: 16px; + overflow: hidden; +} + +.buddy-infos-subitem a { + color: white; + text-decoration: underline; +} + +.tune-note { + background-position: 0 -676px; +} + +.location-world { + background-position: 0 -658px; +} + +.view-individual { + background-position: 0 -34px; +} + +.edit-buddy { + background-position: 0 -1008px; +} + +#buddy-list .filter { + background-color: white; + border-top: 1px solid #b8c2c4; + height: 15px; + padding: 2px 4px; + font-size: 0.8em; + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; + -moz-border-radius-bottomleft: 3px; + -moz-border-radius-bottomright: 3px; + -webkit-border-bottom-left-radius: 3px; + -webkit-border-bottom-right-radius: 3px; +} + +#buddy-list .filter input { + border: none; + color: #273a3f; + width: 211px; + padding: 0; + box-shadow: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; +} + +#buddy-list .filter a { + display: none; + background-color: #9a2d2d; + color: white; + height: 13px; + width: 13px; + margin-top: 1px; + font-size: 0.8em; + text-align: center; + text-decoration: none; + float: right; + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; +} + +#buddy-list .filter a:hover, +#buddy-list .filter a:focus { + background-color: #8c2121; +} + +#buddy-list .filter a:active { + background-color: #7e1919; +} + +#buddy-list .foot { + padding: 9px 1px 3px; +} + +#buddy-list .buddy-list-icon { + height: 16px; + width: 16px; + margin: -3px 5px 0 0; + float: left; +} + +#buddy-list .buddy-list-icon a.talk-images { + height: 16px; + width: 16px; + display: block; +} + +#buddy-list .add, +#page-engine .text .tools-add { + background-position: 0 -1047px; +} + +#buddy-list .join { + background-position: 0 -1065px; +} + +#buddy-list .groupchat, +#page-switch .groupchat-default { + background-position: 0 -1082px; +} + +#buddy-list .more { + background-position: 0 -1100px; +} + +#buddy-list .foot-edit-finish a { + color: white; + font-size: 0.8em; + margin: -3px 4px 0 0; + float: right; + display: block; +} + +#buddy-list .foot-edit-finish a:hover, +#buddy-list .foot-edit-finish a:focus { + text-decoration: underline; + cursor: pointer; +} + +.buddy-conf-item { + position: absolute; + width: 263px; + color: white; + z-index: 9998; + text-align: left; + font-size: 0.8em; + margin-left: -10px; +} + +.buddy-conf-item:hover { + cursor: default; +} + +.buddy-conf-subarrow { + background-position: 0 -241px; + opacity: 0.8; + height: 10px; + width: 18px; + margin-left: 9px; +} + +.buddy-conf-subitem { + background-color: rgb(0,0,0); + background-color: rgba(0,0,0,0.8); + padding: 10px; + text-shadow: 0 1px 1px black; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; +} + +.buddy-conf-p { + margin-bottom: 4px; + width: 220px; + font-weight: bold; + float: left; +} + +.buddy-conf-input { + padding-top: 2px; +} + +.buddy-conf-text { + font-size: 11px; + clear: both; + margin-bottom: 3px; +} + +.buddy-conf-text a { + color: white; +} + +.buddy-conf-text a:hover, +.buddy-conf-text a:focus { + cursor: pointer; + text-decoration: underline; +} + +.buddy-conf-text a.buddy-conf-add-search { + text-decoration: underline; + margin-top: 6px; + display: block; +} + +.buddy-conf-select { + font-size: 1.1em; + clear: both; + margin-bottom: 8px; + width: 180px; + height: 23px; +} + +.join-jid { + width: 220px; + margin-top: 5px; +} + +.add-contact-jid, +.add-contact-name, +.add-contact-gateway { + width: 156px; + margin-bottom: 4px; +} + +.add-contact-name-get { + font-size: 0.8em; + display: none; +} + +.buddy-conf-subitem label { + clear: both; +} + +.buddy-conf-subitem label span { + width: 76px; + height: 14px; + margin-top: 3px; + overflow: hidden; + float: left; +} + +#buddy-conf-join ul { + width: 224px; + max-height: 160px; + left: 10px; + top: 51px; +} + +.buddy-conf-join-select { + margin: 8px 0 0 0; +} diff --git a/jappixmini/jappix/css/channel.css b/jappixmini/jappix/css/channel.css new file mode 100644 index 00000000..671d25ea --- /dev/null +++ b/jappixmini/jappix/css/channel.css @@ -0,0 +1,545 @@ +/* + +Jappix - An open social platform +This is the channel CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 26/08/11 + +*/ + +#channel .top div.update { + position: absolute; + top: 12px; + left: 115px; + right: 15px; + bottom: 15px; + border-radius: 20px; + -moz-border-radius: 20px; + -webkit-border-radius: 20px; +} + +#channel .top p { + font-size: 0.9em; + margin-bottom: 10px; +} + +#channel .top h2 { + font-size: 1.6em; + margin-bottom: 10px; + color: #232323; +} + +#channel .top a { + font-size: 0.9em; + color: #232323; +} + +#channel .top.individual div.update { + right: 36px; +} + +#channel .top.individual div.shortcuts, +#userinfos .main-infos div.shortcuts { + width: 16px; + float: right; +} + +#channel .top.individual div.shortcuts { + margin: 2px 5px 0 0; +} + +#channel .top.individual div.shortcuts a, +#userinfos .main-infos div.shortcuts a { + height: 16px; + width: 16px; + margin-bottom: 4px; + display: block; +} + +#channel .top.individual div.shortcuts a.message, +#userinfos .main-infos a.message { + background-position: 0 -1717px; +} + +#channel .top.individual div.shortcuts a.chat, +#userinfos .main-infos a.chat { + background-position: 0 -1737px; +} + +#channel .top.individual div.shortcuts a.command, +#userinfos .main-infos a.command { + background-position: 0 -1758px; +} + +#channel .microblog-body { + height: 20px; + margin-right: 50px; +} + +#channel .microblog-body input { + width: 100%; + height: 100%; + padding: 8px; +} + +#channel .one-microblog-icon { + position: absolute; + top: 38px; + right: 0; +} + +#channel div.update .one-microblog-icon, +#channel div.update .postit { + width: 16px; + height: 16px; + display: block; +} + +#channel div.update .attach { + background-position: 0 -79px; + display: none; +} + +#attach { + position: absolute; + width: 263px; + margin-left: -227px; + color: white; + font-size: 0.85em; + z-index: 9998; + text-align: left; + display: none; +} + +#attach p { + margin-bottom: 6px !important; +} + +#attach input[type=submit] { + margin: 8px 0 6px 0; +} + +#attach .wait { + float: right; + margin: 7px 5px; +} + +#attach div.one-file { + height: 16px; + margin-top: 2px; +} + +#attach div.one-file a.link { + color: white; + width: 215px; + height: 14px; + margin-left: 2px; + overflow: hidden; + float: left; +} + +#attach div.one-file a.remove { + width: 16px; + height: 16px; + float: left; +} + +.attach-subarrow { + background-position: 0 -241px; + opacity: 0.8; + height: 10px; + width: 18px; + margin-left: 226px; +} + +.attach-subitem { + background-color: rgb(0,0,0); + background-color: rgba(0,0,0,0.8); + padding: 10px; + text-shadow: 0 1px 1px black; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; +} + +.attach-p { + font-weight: bold; + float: left; +} + +#channel .one-update { + margin-bottom: 12px; + padding: 6px 6px 8px 6px; + border-bottom: 1px dotted #d0d0d0; + min-height: 50px; + color: black; + position: relative; + display: none; +} + +#channel .one-update .avatar-container { + text-align: center; + margin-right: 16px; + float: left; + height: 50px; + width: 50px; +} + +#channel .one-update .avatar-container:hover { + cursor: pointer; +} + +#channel .one-update img.avatar { + max-height: 50px; + max-width: 50px; +} + +#channel .one-update div.body { + margin-left: 65px; + opacity: 0.8; +} + +#channel .one-update:hover div.body { + opacity: 1; +} + +#channel .one-update a.repeat { + background-position: 0 -1681px; + height: 16px; + width: 16px; + margin-right: 4px; + float: left; +} + +#channel .one-update span a { + text-decoration: underline; +} + +#channel .one-update p { + display: block; + margin: 0 12px 5px 0; +} + +#channel .one-update p b.name:hover { + cursor: pointer; + text-decoration: underline; +} + +#channel .one-update p.infos { + font-size: 0.9em; +} + +#channel .one-update p.infos a.geoloc { + background-position: 0 -1778px; + color: #363636; + margin-left: 18px; + padding-left: 14px; +} + +#channel .one-update p.infos a.geoloc:hover, +#channel .one-update p.infos a.geoloc:focus, +#channel .one-update p.infos a.geoloc:active { + color: #141414; +} + +#channel .one-update p.file { + font-size: 0.9em; + margin: 6px 0 5px 10px; +} + +#channel .one-update p.file a.link, +#inbox .inbox-new-file a.file { + min-height: 16px; + padding-left: 22px; + text-decoration: underline; + display: block; +} + +#channel .one-update p.file a.link { + margin-top: 4px; +} + +#channel p.file a, +#inbox .inbox-new-file a.file { + background-position: 0 -988px; +} + +#channel p.file a.audio, +#inbox .inbox-new-file a.file.audio { + background-position: 0 -899px; +} + +#channel p.file a.image, +#inbox .inbox-new-file a.file.image { + background-position: 0 -917px; +} + +#channel p.file a.video, +#inbox .inbox-new-file a.file.video { + background-position: 0 -935px; +} + +#channel p.file a.document, +#inbox .inbox-new-file a.file.document { + background-position: 0 -953px; +} + +#channel p.file a.package, +#inbox .inbox-new-file a.file.package { + background-position: 0 -971px; +} + +#channel .one-update p.file a.thumb img { + border: 1px solid #a2a2a2; + max-width: 140px; + max-height: 105px; + margin: 4px 10px 2px 0; + padding: 1px; +} + +#channel .one-update p.file a.thumb img:hover { + border-color: #464646; +} + +#channel .one-update div.comments, +.popup.large div.comments { + width: 410px; + margin: 2px 0 2px 76px; +} + +#channel .one-update div.comments div.arrow, +.popup.large div.comments div.arrow { + background-position: 0 -1702px; + width: 20px; + height: 8px; + margin-left: 20px; + display: block; +} + +#channel .one-update div.comments div.comments-content, +.popup.large div.comments div.comments-content { + background-color: #e5ebec; + color: black; + font-size: 0.9em; + text-shadow: 0 1px 0 white; +} + +#channel .one-update div.comments input, +.popup.large div.comments input { + width: 356px; + margin: 6px 0; + padding: 4px 5px; +} + +#channel .one-update div.comments span.icon, +.popup.large div.comments span.icon { + background-position: 0 -1082px; + height: 16px; + width: 16px; + margin: 10px; + float: left; +} + +#channel .one-update div.comments .one-comment.loading span.icon, +.popup.large div.comments .one-comment.loading span.icon { + margin: 0 10px 0 0; +} + +#channel .one-update div.comments .one-comment, +.popup.large div.comments .one-comment { + border-bottom: 1px solid #f4f4f4; + padding: 4px 8px 0px 8px; + position: relative; + display: block; +} + +#channel .one-update div.comments .one-comment.compose, +.popup.large div.comments .one-comment.compose { + border-bottom: 2px solid #f4f4f4; + height: 36px; + padding: 0; +} + +#channel .one-update div.comments .one-comment.new, +.popup.large div.comments .one-comment.new { + display: none; +} + +#channel .one-update div.comments a.one-comment, +.popup.large div.comments a.one-comment { + text-decoration: none; +} + +#channel .one-update div.comments a.one-comment:hover, +#channel .one-update div.comments a.one-comment:focus, +.popup.large div.comments a.one-comment:hover, +.popup.large div.comments a.one-comment:focus { + text-decoration: underline; +} + +#channel .one-update div.comments .one-comment.loading, +.popup.large div.comments .one-comment.loading { + padding-bottom: 5px; +} + +#channel .one-update div.comments .one-comment div.marker, +.popup.large div.comments .one-comment div.marker { + background-color: #6d8387; + width: 2px; + position: absolute; + top: 0; + left: 0; + bottom: 0; +} + +#channel .one-update div.comments .one-comment .avatar-container, +.popup.large div.comments .one-comment .avatar-container { + text-align: center; + width: 30px; + height: 30px; + margin: 2px 8px 0 0; + float: left; +} + +#channel .one-update div.comments .one-comment .avatar-container:hover, +.popup.large div.comments .one-comment .avatar-container:hover { + cursor: pointer; +} + +#channel .one-update div.comments .one-comment img.avatar, +.popup.large div.comments .one-comment img.avatar { + max-height: 30px; + max-width: 30px; +} + +#channel .one-update div.comments .one-comment .comment-container, +.popup.large div.comments .one-comment .comment-container { + float: left; +} + +#channel .one-update div.comments .one-comment a.name, +.popup.large div.comments .one-comment a.name { + font-weight: bold; + text-decoration: none; + font-size: 0.95em; + padding-bottom: 2px; + float: left; +} + +#channel .one-update div.comments .one-comment a.name:hover, +#channel .one-update div.comments .one-comment a.name:focus, +.popup.large div.comments .one-comment a.name:hover, +.popup.large div.comments .one-comment a.name:focus { + text-decoration: underline; +} + +#channel .one-update div.comments .one-comment span.date, +#channel .one-update div.comments .one-comment a.remove, +.popup.large div.comments .one-comment span.date, +.popup.large div.comments .one-comment a.remove { + font-size: 0.85em; + float: right; +} + +#channel .one-update div.comments .one-comment.me:hover span.date, +.popup.large div.comments .one-comment.me:hover span.date { + display: none; +} + +#channel .one-update div.comments .one-comment.me a.remove, +.popup.large div.comments .one-comment.me a.remove { + display: none; +} + +#channel .one-update div.comments .one-comment.me:hover a.remove, +.popup.large div.comments .one-comment.me:hover a.remove { + display: block; +} + +#channel .one-update div.comments .one-comment p.body, +.popup.large div.comments .one-comment p.body { + clear: both; +} + +#channel a.more { + background-position: 0 -334px; + color: black; + height: 16px; + text-decoration: none; + margin: -2px 0 0 4px; + padding: 0 0 14px 20px; + display: block; + visibility: hidden; +} + +#channel a.more:hover, +#channel a.more:focus { + text-decoration: underline; +} + +#channel a.mbtool { + width: 11px; + height: 11px; + display: none; + position: absolute; + right: 0; +} + +#channel .one-update:hover a.mbtool { + display: block; +} + +#channel a.mbtool:hover, +#channel a.mbtool:focus { + text-decoration: none; +} + +#channel a.mbtool.profile { + background-position: -1px -1333px; + top: 24px; +} + +#channel a.mbtool.repost { + background-position: -1px -1354px; +} + +#channel a.mbtool.remove { + background-position: -1px -1312px; +} + +#channel a.mbtool.repost, +#channel a.mbtool.remove { + top: 6px; +} + +#channel .footer { + bottom: 0; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-bottomright: 4px; + -webkit-border-bottom-left-radius: 4px; + -webkit-border-bottom-right-radius: 4px; +} + +#channel .footer div { + margin-left: 5px; + padding-left: 24px; + min-height: 16px; + font-size: 0.85em; + width: auto !important; +} + +#channel .footer .sync { + background-position: 0 -804px; + display: none; +} + +#channel .footer .unsync { + background-position: 0 -830px; + display: none; +} diff --git a/jappixmini/jappix/css/directory.css b/jappixmini/jappix/css/directory.css new file mode 100644 index 00000000..34b32312 --- /dev/null +++ b/jappixmini/jappix/css/directory.css @@ -0,0 +1,16 @@ +/* + +Jappix - An open social platform +This is the directory tool CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 13/02/11 + +*/ + +#directory .content { + padding: 10px 0 10px 0; +} diff --git a/jappixmini/jappix/css/discovery.css b/jappixmini/jappix/css/discovery.css new file mode 100644 index 00000000..f66456b6 --- /dev/null +++ b/jappixmini/jappix/css/discovery.css @@ -0,0 +1,61 @@ +/* + +Jappix - An open social platform +This is the discovery CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 13/02/11 + +*/ + +#discovery .content { + padding: 10px 0 10px 0; +} + +#discovery .content p { + margin: 5px 10px 5px 10px; + text-align: justify; + font-size: 0.85em; +} + +#discovery .discovery-head, +#directory .directory-head, +#rosterx .rosterx-head, +#privacy .privacy-head { + width: 606px; + height: 24px; + margin: 0 10px 10px 10px; + padding: 6px; + background: #f1f6fd; + border: 1px #9dc4fc solid; +} + +#discovery .disco-server-text, +#directory .directory-server-text { + float: left; + font-size: 0.9em; + margin: 3px; +} + +#discovery .disco-server-input, +#directory .directory-server-input { + float: right; + width: 200px; + padding: 2px; + height: 18px; + float: right; + margin-right: 10px; + padding: 2px; +} + +#discovery .disco-category { + display: none; + margin-bottom: 22px; +} + +#discovery .disco-category-title { + font-weight: bold; +} diff --git a/jappixmini/jappix/css/favorites.css b/jappixmini/jappix/css/favorites.css new file mode 100644 index 00000000..5d3fd51d --- /dev/null +++ b/jappixmini/jappix/css/favorites.css @@ -0,0 +1,179 @@ +/* + +Jappix - An open social platform +This is the favorites CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 28/12/10 + +*/ + +#favorites .content { + padding: 10px 0 10px 0; +} + +#favorites .fedit-head-select { + min-width: 190px; + max-width: 210px; +} + +#favorites .switch-fav { + margin: 0 10px 0 10px; + width: 200px; + height: 355px; + border-right: 1px #c0c0c0 dotted; + float: left; +} + +#favorites .room-switcher { + width: 188px; + height: 18px; + border-bottom: 1px #9dc4fc solid; + float: left; + padding: 10px 6px; + font-size: 0.9em; +} + +#favorites .room-switcher:hover { + background-color: #e9f1fd; + cursor: pointer; +} + +#favorites .room-switcher:active { + background-color: #f1f6fd; +} + +#favorites .switch-fav .icon { + float: left; + height: 16px; + width: 16px; + margin: 0 8px 0 0; +} + +#favorites .switch-fav .room-list .list-icon { + background-position: 0 -855px; +} + +#favorites .switch-fav .room-search .search-icon { + background-position: 0 -876px; +} + +#favorites .static-fav { + width: 385px; + height: 335px; + margin: 0 10px 0 0; + padding: 10px; + float: right; +} + +#favorites .favorites-search { + display: none; +} + +#favorites .static-fav-head { + width: 393px; + margin: -10px; +} + +#favorites .static-fav-results { + width: 406px; + height: 314px; + margin: 10px -10px -10px -10px; + padding: 6px 0 0 0; +} + +#favorites .fedit-line { + height: 30px; + font-size: 0.9em; + padding: 10px 0 4px 4px; + border-bottom: 1px #9dc4fc solid; +} + +#favorites .fedit-line:hover { + background: #e9f1fd; +} + +#favorites label { + width: 140px; + margin-top: 3px; +} + +#favorites input { + height: 18px; + width: 186px; + margin-top: 0; + padding: 2px; +} + +#favorites .fedit-select { + min-width: 160px; +} + +#favorites .fedit-actions { + margin: 10px 0 0; + font-size: 0.9em; + float: right; +} + +#favorites input[type=checkbox] { + margin-top: 5px; +} + +#favorites .fedit-terminate { + float: right; +} + +#favorites .fedit-add { + display: block; +} + +#favorites .fedit-edit { + background-position: 2px -1240px; +} + +#favorites .fedit-remove { + margin: 0 8px 0 0; +} + +#favorites .add, +.popup .results .one-button.one-add { + background-position: 3px -1177px; +} + +#favorites .remove, +#inbox .remove { + background-position: 3px -1196px; +} + +#favorites .join, +#inbox .reply, +#inbox .send, +.popup .results .one-button.one-chat { + background-position: 3px -124px; +} + +#favorites .one-button, +#inbox .one-button, +.popup .results .one-button { + padding-left: 20px !important; + font-size: 0.98em; +} + +#favorites .fsearch-results { + overflow: auto; +} + +#favorites .room-name { + margin: 4px 2px 5px; + max-width: 210px; + float: left; +} + +#favorites .fsearch-noresults { + display: none; + font-size: 0.9em; + font-weight: bold; +} diff --git a/jappixmini/jappix/css/home.css b/jappixmini/jappix/css/home.css new file mode 100644 index 00000000..789ea4d9 --- /dev/null +++ b/jappixmini/jappix/css/home.css @@ -0,0 +1,579 @@ +/* + +Jappix - An open social platform +This is the home CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 15/01/12 + +*/ + +#home { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + min-height: 550px; + min-width: 875px; +} + +#home .corporation, +#home .corporation .corp_network, +#home .locale, +#home .obsolete { + background-color: rgb(20,20,20); + background-color: rgba(20,20,20,0.70); + color: white; + position: absolute; + top: 0; + text-shadow: 0 0 1px black; + z-index: 100; + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; + -moz-border-radius-bottomleft: 3px; + -moz-border-radius-bottomright: 3px; + -webkit-border-bottom-left-radius: 3px; + -webkit-border-bottom-right-radius: 3px; +} + +#home .corporation { + background-position: 9px -357px; + left: 12px; + height: 26px; + width: 34px; +} + +#home .corporation.hovered { + height: 28px; + border-radius: 0; + -moz-border-radius: 0; + -webkit-border-radius: 0; +} + +#home .corporation .corp_network { + width: 180px; + padding: 10px 12px; + top: 28px; + display: none; + border-top-right-radius: 3px; + -moz-border-radius-topright: 3px; + -webkit-border-top-right-radius: 3px; +} + +#home .corporation.hovered .corp_network { + display: block; +} + +#home .corporation .corp_network h2 { + font-size: 1.1em; + margin: 14px 0 4px 0; +} + +#home .corporation .corp_network h2.nomargin { + margin-top: 0; +} + +#home .corporation .corp_network a { + font-size: 0.8em; + margin: 2px 0; + padding: 2px 6px; + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; +} + +#home .corporation .corp_network a span { + margin: 2px 0; + display: block; +} + +#home .corporation .corp_network a span.name { + font-weight: bold; +} + +#home .corporation .corp_network a span.desc { + font-size: 0.9em; + margin-left: 2px; +} + +#home .locale { + left: 52px; + font-size: 0.8em; +} + +#home .locale .current { + height: 19px; + padding: 3px 12px 4px 12px; + font-weight: bold; +} + +#home .locale .current:hover { + cursor: default; +} + +#home .locale .current .current_align { + height: 19px; + vertical-align: middle; + display: table-cell; +} + +#home .locale .list { + margin: 2px 0 2px; +} + +#home .locale .list a, +#home .corporation .corp_network a { + color: white; + text-decoration: none; + display: block; +} + +#home .locale .list a { + padding: 3px 10px; +} + +#home .locale .list a:hover, +#home .locale .list a:focus, +#home .corporation .corp_network a:hover, +#home .corporation .corp_network a:focus { + background-color: rgb(255,255,255); + background-color: rgba(255,255,255,0.1); + cursor: pointer; +} + +#home .locale .list a:active, +#home .corporation .corp_network a:active { + background-color: rgb(255,255,255); + background-color: rgba(255,255,255,0.14); +} + +#home .obsolete { + height: 60px; + padding: 4px 10px; + right: 12px; + font-size: 0.9em; + font-weight: bold; + display: none; +} + +#home .obsolete a { + height: 33px; + width: 33px; + margin: 5px 2px 0 0; + float: left; +} + +#home .obsolete a:hover, +#home .obsolete a:focus { + opacity: 0.8; +} + +#home .obsolete a:active { + opacity: 0.6; +} + +#home .obsolete a.firefox { + background-position: 1px 0; +} + +#home .obsolete a.chrome { + background-position: -34px 0; +} + +#home .obsolete a.safari { + background-position: -68px 0; +} + +#home .obsolete a.opera { + background-position: -101px 0; +} + +#home .obsolete a.ie { + background-position: -135px 0; +} + +#home .plane { + background-position: 0 -384px; + width: 507px; + height: 328px; + position: absolute; + left: 0; + top: 60px; +} + +#home .main { + background-color: rgb(20,20,20); + background-color: rgba(20,20,20,0.85); + position: absolute; + top: 50%; + margin-top: -200px; + width: 800px; + height: 400px; + left: 50%; + margin-left: -400px; + z-index: 50; + border-radius: 10px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + box-shadow: 0 0 35px #5c5c5c; + -moz-box-shadow: 0 0 35px #5c5c5c; + -webkit-box-shadow: 0 0 35px #5c5c5c; +} + +#home .left { + float: left; + width: 350px; + height: 370px; + margin: 15px 0 15px 15px; + color: white; + text-align: center; + text-shadow: 0 1px 1px black; +} + +#home .left .logo { + background-position: 0 0; + float: left; + margin: 30px 20px; + width: 311px; + height: 113px; +} + +#home .left p.upper { + margin: 12px 0 20px 0; +} + +#home .left p.secondary { + margin: 8px 0 0 16px; + font-size: 0.9em; + width: 320px; +} + +#home .right { + background: #e4eef9; + background: -moz-linear-gradient(top, #e4eef9, #C5E1FF); + background: -webkit-gradient(linear, left top, left bottom, from(#e4eef9), to(#C5E1FF)); + float: right; + width: 385px; + height: 350px; + margin: 15px 15px 15px 0; + padding: 10px; + font-size: 13px; + text-align: justify; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + box-shadow: 0 0 20px black; + -moz-box-shadow: 0 0 20px black; + -webkit-box-shadow: 0 0 20px black; +} + +#home .right h1 { + font-size: 16px; + padding-bottom: 4px; + border-bottom: 1px black dotted; +} + +#home .right p { + margin-bottom: 4px; +} + +#home .right p a { + border-width: 0 0 1px 0; + border-style: dotted; + border-color: black; +} + +#home .right p a:hover, +#home .right p a:focus { + border-style: solid; + text-decoration: none; +} + +#home .right button { + display: block; + margin-left: 22px; + width: 342px; + height: 64px; + text-decoration: none; + font-weight: bold; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; +} + +#home .right button:hover { + cursor: pointer; +} + +#home .right button span { + float: left; +} + +#home .right button span.home-images { + height: 16px; + width: 16px; + margin: 5px 7px 7px 24px; +} + +#home .right button span.text { + padding-left: 20px; + font-size: 1.5em; +} + +#home .right .login { + background-color: #72d071; + background-position: 0 0; + border: 1px solid #5cb55c; + margin-top: 22px; + box-shadow: 0 0 10px #89e389; + -moz-box-shadow: 0 0 10px #89e389; + -webkit-box-shadow: 0 0 10px #89e389; +} + +#home .right .login:hover, +#home .right .login:focus { + border: 1px solid #419141; + box-shadow: 0 0 15px #72d071; + -moz-box-shadow: 0 0 15px #72d071; + -webkit-box-shadow: 0 0 15px #72d071; +} + +#home .right .login:active { + background-color: #97e896; + background-position: 0 -80px; +} + +#home .right .login span.text { + color: #2d612d; + text-shadow: 1px 1px 1px #5cb55c; +} + +#home .right .login span.home-images { + background-position: 0 -230px; +} + +#home .right .register { + background-color: #f6ef82; + background-position: 0 -160px; + border: 1px solid #e3db56; + margin-top: 15px; + box-shadow: 0 0 15px #f1e968; + -moz-box-shadow: 0 0 15px #f1e968; + -webkit-box-shadow: 0 0 15px #f1e968; +} + +#home .right .register:hover, +#home .right .register:focus { + border: 1px solid #d2c93f; + box-shadow: 0 0 15px #e0d743; + -moz-box-shadow: 0 0 15px #e0d743; + -webkit-box-shadow: 0 0 15px #e0d743; +} + +#home .right .register:active { + background-color: #fdf7af; + background-position: 0 -240px; +} + +#home .right .register span.text { + color: #6d6813; + text-shadow: 1px 1px 1px #dbd56e; +} + +#home .right .register span.home-images { + background-position: 0 -204px; +} + +#home .right p.notice { + margin-top: 24px; + font-size: 0.9em; +} + +#home .right .navigation { + clear: both; + width: 385px; + border-top: 1px black dotted; + position: absolute; + text-align: right; + bottom: 25px; + right: 25px; + padding-top: 6px; +} + +#home .right .navigation a { + margin-left: 9px; + color: black; + text-decoration: none; + font-size: 0.9em; + height: 12px; + padding: 0 0 4px 20px; + float: right; +} + +#home .right .navigation a:hover, +#home .right .navigation a:focus { + text-decoration: underline; +} + +#home .right .navigation a.unencrypted { + background-position: 0 -256px; +} + +#home .right .navigation a.encrypted { + background-position: 0 -282px; +} + +#home .right .navigation a.project { + background-position: 0 -126px; +} + +#home .right .navigation a.manager { + background-position: 0 -152px; +} + +#home .right .navigation a.mobile { + background-position: 0 -178px; +} + +#home a.advanced { + background-position: 0 -334px; + font-size: 0.9em; + height: 16px; + margin-bottom: 10px; + padding-left: 16px; + display: block; +} + +#home fieldset.advanced { + display: none; +} + +#home .anonymouser input[type=text] { + width: 160px; +} + +#home .homediv.registerer .success a { + font-weight: bold; + text-decoration: underline; +} + +#home fieldset { + border: 1px solid black; + margin: 12px 0 12px 0; + padding: 5px 0 4px 0; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; +} + +#home legend { + font-size: 0.9em; + margin: 0 0 0 15px; + padding: 0 2px; + text-transform: uppercase; +} + +#home label { + width: 110px; + display: block; + float: left; + clear: both; + margin: 0 0 5px 12px; +} + +#home input, +#home select { + float: left; + margin-bottom: 5px; +} + +#home input[type=text], +#home input[type=password], +#home select { + width: 140px; + margin-top: -2px; +} + +#home input[type=submit] { + min-width: 120px; + float: right; +} + +#home span.jid { + display: block; + float: left; + margin: 0 4px; +} + +#home input.nick, #home input.server { + width: 110px; +} + +#home .info { + padding: 6px; + position: absolute; + bottom: 62px; + right: 35px; + border-width: 1px; + border-style: dotted; + clear: both; + width: 350px; +} + +#home .info.success { + background-color: #aee578; + border-color: #85b05c; + display: none; +} + +#home .info.fail { + background-color: #f19d9d; + border-color: #b34f4f; +} + +#home .info.report { + background-color: #f3f48b; + border-color: #c9c66b; + display: none; +} + +#home .info.report span { + text-decoration: underline; +} + +#home .notice.simple { + background-color: rgb(20,20,20); + background-color: rgba(20,20,20,0.7); + color: white; + font-size: 0.9em; + text-decoration: none; + text-shadow: 0 1px 0 black; + position: fixed; + bottom: 0; + left: 0; + right: 0; + padding: 8px 20px; + z-index: 100; + box-shadow: 0 0 25px #ababab; + -moz-box-shadow: 0 0 25px #ababab; + -webkit-box-shadow: 0 0 25px #ababab; +} + +#home .notice.simple .title { + background-color: rgb(20,20,20); + background-color: rgba(20,20,20,0.4); + background-position: 8px -299px; + border-width: 0 1px 1px 1px; + border-style: solid; + border-color: #141414; + font-weight: bold; + padding: 8px 8px 8px 30px; +} + +#home .notice.simple .text { + margin-left: 20px; +} diff --git a/jappixmini/jappix/css/ie.css b/jappixmini/jappix/css/ie.css new file mode 100644 index 00000000..8808b50b --- /dev/null +++ b/jappixmini/jappix/css/ie.css @@ -0,0 +1,146 @@ +/* + +Jappix - An open social platform +These are all the IE compliant CSS classes + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 22/04/11 + +*/ + +/* rgba(255,255,255,0.9) */ +.search ul { + background: transparent; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#edffffff,endColorstr=#edffffff); +} + +/* rgba(255,255,255,0.3) */ +a.finish:active, +#manager-buttons input:active, +#install-buttons input:active { + background: transparent; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#49ffffff,endColorstr=#49ffffff); +} + +/* rgba(255,255,255,0.2) */ +a.finish:hover, +a.finish:focus, +#manager-buttons input:hover, +#manager-buttons input:focus, +#install-buttons input:hover, +#install-buttons input:focus, +.notifications-content .one-notification:active { + background: transparent; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#33ffffff,endColorstr=#33ffffff); +} + +/* rgba(255,255,255,0.14) */ +#home .locale .list a:active { + background: transparent; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#2fffffff,endColorstr=#2fffffff); +} + +/* rgba(255,255,255,0.1) */ +#home .locale .list a:hover, +#home .locale .list a:focus, +a.finish, +a.finish.disabled:hover, +a.finish.disabled:focus, +a.finish.disabled:active, +#manager-buttons input, +#install-buttons input, +.notifications-content .one-notification:hover, +.notifications-content .one-notification:focus { + background: transparent; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#20ffffff,endColorstr=#20ffffff); +} + +/* rgba(255,239,104,0.8) */ +.popup .infos { + background: transparent; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#deffef68,endColorstr=#deffef68); +} + +/* rgba(225,160,20,0.3) */ +.search ul li.hovered { + background: transparent; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#46e1a014,endColorstr=#46e1a014); +} + +/* rgba(248,246,186,0.9) */ +#board .one-board.info { + background: transparent; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#edf8f6ba,endColorstr=#edf8f6ba); +} + +/* rgba(241,160,160,0.9) */ +#board .one-board.error { + background: transparent; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#edf1a0a0,endColorstr=#edf1a0a0); +} + +/* rgba(234,234,234,0.8) */ +#page-engine .chatstate { + background: transparent; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#deeaeaea,endColorstr=#deeaeaea); +} + +/* rgba(20,20,20,0.6) */ +#home .locale, +#home .obsolete, +#home .notice.simple { + background: transparent; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#a0141414,endColorstr=#a0141414); +} + +/* rgba(20,20,20,0.8) */ +#home .main, +#reconnect .pane, +#my-infos, +#right-content, +#buddy-list, +#manager, +#install { + background: transparent; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#de141414,endColorstr=#de141414); +} + +/* rgba(20,20,20,0.9) */ +.popup { + background: transparent; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#ed141414,endColorstr=#ed141414); +} + +/* rgba(0,0,0,0.2) */ +#install-top .step { + background: transparent; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#33000000,endColorstr=#33000000); +} + +/* rgba(0,0,0,0.6) */ +.lock { + background: url(../img/others/lock.png) repeat !important; +} + +/* rgba(0,0,0,0.8) */ +#page-engine .tooltip-subitem, +.attach-subitem, +.buddy-infos-subitem, +.buddy-conf-subitem, +.tools-content-subitem { + background: transparent; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#de000000,endColorstr=#de000000); +} + +/* Fix a fieldset padding bug */ +legend { + margin-bottom: 5px !important; +} + +/* Fix an opacity bug */ +a.finish.disabled { + filter: alpha(opacity = 20) !important; +} diff --git a/jappixmini/jappix/css/images.css b/jappixmini/jappix/css/images.css new file mode 100644 index 00000000..2dfb756f --- /dev/null +++ b/jappixmini/jappix/css/images.css @@ -0,0 +1,89 @@ +/* + +Jappix - An open social platform +This is the images CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 16/01/12 + +*/ + +.body-images { + background-image: url(../img/sprites/background.png); + background-repeat: repeat; + background-color: #93c5fa; +} + +.install-images { + background-image: url(../img/sprites/install.png); + background-repeat: no-repeat; +} + +.home-images { + background-image: url(../img/sprites/home.png); + background-repeat: no-repeat; +} + +.browsers-images { + background-image: url(../img/sprites/browsers.png); + background-repeat: no-repeat; +} + +.buttons-images { + background-image: url(../img/sprites/buttons.png); + background-repeat: repeat-x; +} + +.talk-images { + background-image: url(../img/sprites/talk.png); + background-repeat: no-repeat; +} + +.smileys-images { + background-image: url(../img/sprites/smileys.png); + background-repeat: no-repeat; +} + +.welcome-images { + background-image: url(../img/sprites/welcome.png); + background-repeat: no-repeat; +} + +.me-images { + background-image: url(../img/sprites/me.png); + background-repeat: no-repeat; +} + +.manager-images { + background-image: url(../img/sprites/manager.png); + background-repeat: no-repeat; +} + +.mobile-images { + background-image: url(../img/sprites/mobile.png); + background-repeat: no-repeat; +} + +.wait-small { + background-image: url(../img/wait/wait-small.gif); + background-repeat: no-repeat; + height: 16px; + width: 16px; +} + +.wait-medium { + background-image: url(../img/wait/wait-medium.png); + background-repeat: no-repeat; + height: 24px; + width: 24px; +} + +.wait-big { + background-image: url(../img/wait/wait-big.gif); + background-repeat: no-repeat; + height: 30px; + width: 30px; +} diff --git a/jappixmini/jappix/css/inbox.css b/jappixmini/jappix/css/inbox.css new file mode 100644 index 00000000..1ef05886 --- /dev/null +++ b/jappixmini/jappix/css/inbox.css @@ -0,0 +1,202 @@ +/* + +Jappix - An open social platform +This is the inbox CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 28/12/10 + +*/ + +#inbox .content { + padding: 10px 0 10px 0; +} + +#inbox .content p { + margin: 3px 10px; + text-align: justify; + font-size: 0.9em; +} + +#inbox .inbox-results { + height: 310px; + width: 620px; + margin: -5px 0 0 10px; + padding: 6px 0 0 0; + overflow: auto; +} + +#inbox .message-unread { + background-color: #E9F1FD; +} + +#inbox .one-message { + font-size: 0.9em; + border-bottom: 1px #b2c7cb solid; +} + +#inbox .message-head { + padding: 6px 0 7px 4px; + overflow: hidden; +} + +#inbox .message-head:hover { + background-color: #e9f1fd; + cursor: pointer; +} + +#inbox .message-head:active { + background-color: #f1f6fd; +} + +#inbox .one-message.message-reading, +#inbox .one-message.message-reading .message-head { + background-color: #f1f6fd; +} + +#inbox .avatar-container { + float: left; + width: 40px; + height: 40px; + margin-right: 7px; + text-align: center; + background-repeat: no-repeat; +} + +#inbox .avatar { + max-width: 40px; + max-height: 40px; +} + +#inbox .message-jid, +#inbox .message-subject { + float: left; + margin: 0 2px; + overflow: hidden; +} + +#inbox .message-jid { + width: 165px; + font-weight: bold; +} + +#inbox .message-subject { + width: 355px; +} + +#inbox .message-truncated { + color: #42646b; + font-size: 0.8em; + margin: 23px 0 0 49px; +} + +#inbox .message-body { + padding: 8px 5px 5px 5px; +} + +#inbox .message-body a { + text-decoration: underline; +} + +#inbox .message-meta { + margin-top: 6px; + padding: 3px 4px; + border-top: 1px #b2c7cb dotted; +} + +#inbox .message-meta span.date { + color: #28474e; + font-size: 0.8em; + margin: 10px 0 0 4px; + float: left; +} + +#inbox .message-meta a { + font-size: 0.98em; + margin: 5px; + float: right; + display: block; +} + +#inbox .inbox-noresults { + font-weight: bold; + display: none; +} + +#inbox .a-show-messages { + display: none; +} + +#inbox .inbox-new { + display: none; + height: 300px; + width: 620px; + margin: -5px 0 0 10px; + padding: 16px 0 0 0; +} + +#inbox .inbox-new-block { + border-top: 1px #686868 dotted; + padding-top: 9px; + min-height: 32px; + clear: both; +} + +#inbox .inbox-new-text { + float: left; + width: 100px; +} + +#inbox .inbox-new-textarea { + width: 460px; + height: 109px; + margin-bottom: 10px; + float: left; +} + +#inbox .inbox-new input { + float: left; +} + +#inbox .inbox-new-to ul { + width: 264px; + max-height: 168px; + font-size: 0.9em; + left: 120px; + top: 31px; +} + +#inbox .inbox-new-to-input { + width: 260px; +} + +#inbox .inbox-new-subject-input { + width: 380px; +} + +#inbox .inbox-new-file a { + display: block; + float: left; +} + +#inbox .inbox-new-file a.file { + font-size: 0.85em; + height: 16px; + max-width: 320px; + margin: 3px 0 15px 013px; + overflow: hidden; +} + +#inbox .inbox-new-file a.one-button { + font-size: 0.85em; + margin: -2px 0 0 25px; +} + +#inbox .inbox-new-send a { + font-size: 0.85em; + float: right; + display: block; +} diff --git a/jappixmini/jappix/css/install.css b/jappixmini/jappix/css/install.css new file mode 100644 index 00000000..9dbde3fc --- /dev/null +++ b/jappixmini/jappix/css/install.css @@ -0,0 +1,285 @@ +/* + +Jappix - An open social platform +This is the install CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 08/06/11 + +*/ + +body { + color: white; +} + +#install { + background-color: rgb(20,20,20); + background-color: rgba(20,20,20,0.85); + width: 800px; + margin: 35px auto; + padding-bottom: 17px; + border-radius: 6px; + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + box-shadow: 0 0 35px #5c5c5c; + -moz-box-shadow: 0 0 35px #5c5c5c; + -webkit-box-shadow: 0 0 35px #5c5c5c; +} + +#install a { + color: black; + text-decoration: underline; +} + +#install .clear { + clear: both; +} + +#install fieldset { + border: 1px solid black; + margin: 22px 0 15px 0; + padding: 7px 2px 5px 2px; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; +} + +#install legend { + font-size: 0.9em; + margin: 0 0 0 15px; + padding: 0 2px; + text-transform: uppercase; +} + +#install label { + width: 200px; + display: block; + float: left; + clear: both; + margin: 0 0 9px 12px; +} + +#install input { + float: left; + margin-bottom: 5px; +} + +#install input[type=text], +#install input[type=url], +#install input[type=password] { + margin-top: -2px; + padding: 3px; + font-size: 0.95em; + min-width: 220px; +} + +#install input.icon { + padding-left: 24px; + min-width: 199px; + max-height: 18px; +} + +#install input.icon#user_name { + background-position: 4px -204px; +} + +#install input.icon#user_password { + background-position: 4px -226px; +} + +#install input.icon#user_repassword { + background-position: 4px -248px; +} + +#install-top { + padding: 30px 45px; +} + +#install-top .logo { + background-position: 0 0; + min-width: 88px; + height: 36px; + padding: 32px 0 0 66px; + font-size: 32px; + color: white; + text-transform: lowercase; + float: left; + text-shadow: 0 1px 1px black; +} + +#install-top .step { + background-color: rgb(0,0,0); + background-color: rgba(0,0,0,0.2); + border: 2px solid white; + padding: 6px 21px; + font-size: 2.7em; + text-shadow: 0 1px 1px black; + float: right; + border-radius: 40px; + -moz-border-radius: 40px; + -webkit-border-radius: 40px; + box-shadow: 0 0 10px #202020; + -moz-box-shadow: 0 0 10px #202020; + -webkit-box-shadow: 0 0 10px #202020; +} + +#install-top .step span { + font-size: 0.6em; +} + +#install-content { + background: #e4eef9; + background: -moz-linear-gradient(top, #e4eef9, #d0e5fa); + background: -webkit-gradient(linear, left top, left bottom, from(#e4eef9), to(#d0e5fa)); + color: black; + font-size: 0.9em; + margin: 0 10px; + padding: 20px 24px; + min-height: 260px; + clear: both; + right: 10px; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + box-shadow: 0 0 20px #202020; + -moz-box-shadow: 0 0 20px #202020; + -webkit-box-shadow: 0 0 20px #202020; +} + +#install-content h3 { + padding-left: 24px; + margin-bottom: 15px; +} + +#install-content h3.start { + background-position: 0 -73px; +} + +#install-content h3.storage { + background-position: 0 -95px; +} + +#install-content h3.account { + background-position: 0 -117px; +} + +#install-content h3.main { + background-position: 0 -139px; +} + +#install-content h3.hosts { + background-position: 0 -161px; +} + +#install-content h3.services { + background-position: 0 -183px; +} + +#install-content p { + margin-bottom: 10px; +} + +#install-content .info { + color: black; + border-width: 1px; + border-style: dashed; + padding: 6px 8px; + display: block; + text-decoration: none; +} + +#install-content .info.smallspace { + margin: 14px 0 10px 0; +} + +#install-content .info.bigspace { + margin: 35px 0 20px 0; +} + +#install-content .info.first { + margin-top: 28px; +} + +#install-content .info.last { + margin-bottom: 28px; +} + +#install-content .info.neutral { + background-color: #f0f19d; + border-color: #b3ad4f; +} + +#install-content a.info.neutral:hover, +#install-content a.info.neutral:focus { + background-color: #eced96; +} + +#install-content a.info.neutral:active { + background-color: #e9ea93; +} + +#install-content .info.success { + background-color: #a8dca9; + border-color: #5e9f5f; +} + +#install-content a.info.success:hover, +#install-content a.info.success:focus { + background-color: #a0d5a1; +} + +#install-content a.info.success:active { + background-color: #9ad09b; +} + +#install-content .info.fail { + background-color: #f19d9d; + border-color: #b34f4f; +} + +#install-content ol { + margin: 20px 30px; +} + +#install-content ol li { + margin-bottom: 1px; +} + +#install-buttons { + margin-top: 22px; +} + +#install-buttons input { + border: 1px solid white; + background-color: rgb(255,255,255); + background-color: rgba(255,255,255,0.1); + color: white; + padding: 4px 8px; + margin-right: 20px; + text-shadow: 0 1px 1px black; + float: right; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + box-shadow: 0 0 5px #202020; + -moz-box-shadow: 0 0 5px #202020; + -webkit-box-shadow: 0 0 5px #202020; +} + +#install-buttons input:hover, +#install-buttons input:focus { + cursor: pointer; + background-color: rgb(255,255,255); + background-color: rgba(255,255,255,0.2); + box-shadow: 0 0 15px #202020; + -moz-box-shadow: 0 0 15px #202020; + -webkit-box-shadow: 0 0 15px #202020; +} + +#install-buttons input:active { + background-color: rgb(255,255,255); + background-color: rgba(255,255,255,0.3); +} diff --git a/jappixmini/jappix/css/integratebox.css b/jappixmini/jappix/css/integratebox.css new file mode 100644 index 00000000..b5cb9c9f --- /dev/null +++ b/jappixmini/jappix/css/integratebox.css @@ -0,0 +1,34 @@ +/* + +Jappix - An open social platform +This is the integratebox CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 06/11/10 + +*/ + +#integratebox .top { + height: 40px; +} + +#integratebox .content { + text-align: center; + height: 385px; +} + +#integratebox .one-media img { + max-height: 385px; + max-width: 640px; +} + +#integratebox .one-media a img { + border: none; +} + +#integratebox .one-media audio { + margin-top: 170px; +} diff --git a/jappixmini/jappix/css/jquery.datepicker.css b/jappixmini/jappix/css/jquery.datepicker.css new file mode 100644 index 00000000..264cd544 --- /dev/null +++ b/jappixmini/jappix/css/jquery.datepicker.css @@ -0,0 +1,148 @@ +/* + +Jappix - An open social platform +This is the datepicker CSS stylesheet + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 28/12/10 + +*/ + +div.datepicker { + position: relative; + font-family: Arial, Helvetica, sans-serif; + font-size: 12px; + width: 196px; + height: 147px; + position: absolute; + cursor: default; + top: 0; + left: 0; + display: none; +} + +.datepickerHidden { + display: none; +} + +div.datepicker table { + border-collapse:collapse; +} + +div.datepicker a { + text-decoration: none; + cursor: default; + outline: none; +} + +div.datepicker table td { + text-align: right; + padding: 0; + margin: 0; +} + +div.datepicker th { + text-align: center; + color: #47646a; + font-weight: normal; +} + +div.datepicker tbody th { + text-align: left; +} + +div.datepicker tbody a { + display: block; +} + +.datepickerDays a { + width: 20px; + line-height: 16px; + height: 16px; + padding-right: 2px; +} + +.datepickerYears a, +.datepickerMonths a { + width: 44px; + line-height: 36px; + height: 36px; + text-align: center; +} + +td.datepickerNotInMonth { + background: #c7d1d4; +} + +tbody.datepickerDays td.datepickerSelected { + background: #b0bdc1; +} + +tbody.datepickerYears td.datepickerSelected, +tbody.datepickerMonths td.datepickerSelected { + background: #9daaae; +} + +div.datepicker a:hover, +div.datepicker a:focus { + color: #3d7682; +} + +div.datepicker tbody th { + text-align: left; +} + +.datepickerSpace div { + width: 20px; +} + +.datepickerGoNext a, +.datepickerGoPrev a, +.datepickerMonth a { + text-align: center; + height: 20px; + line-height: 20px; +} + +.datepickerGoNext a { + float: right; + width: 20px; +} + +.datepickerGoPrev a { + float: left; + width: 20px; +} + +table.datepickerViewDays tbody.datepickerMonths, +table.datepickerViewDays tbody.datepickerYears { + display: none; +} + +table.datepickerViewMonths tbody.datepickerDays, +table.datepickerViewMonths tbody.datepickerYears, +table.datepickerViewMonths tr.datepickerDoW { + display: none; +} + +table.datepickerViewYears tbody.datepickerDays, +table.datepickerViewYears tbody.datepickerMonths, +table.datepickerViewYears tr.datepickerDoW { + display: none; +} + +td.datepickerDisabled a, +td.datepickerDisabled.datepickerNotInMonth a { + color: #333; +} + +td.datepickerSpecial a { + background: #700; +} + +td.datepickerSpecial.datepickerSelected a { + background: #a00; +} diff --git a/jappixmini/jappix/css/main.css b/jappixmini/jappix/css/main.css new file mode 100644 index 00000000..68254814 --- /dev/null +++ b/jappixmini/jappix/css/main.css @@ -0,0 +1,131 @@ +/* + +Jappix - An open social platform +This is the main CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 05/10/11 + +*/ + +* { + margin: 0; + padding: 0; +} + +body { + font: normal 14.4px Helvetica, Verdana, sans-serif; + text-shadow: 0 0 5px white; +} + +h1 { + margin-bottom: 15px; +} + +a { + text-decoration: none; + color: black; + outline-style: none; +} + +a:hover, +a:focus { + cursor: pointer; + text-decoration: underline; +} + +legend { + color: black; +} + +input, +textarea { + background-color: white; + border: 1px solid #636363; + font-size: 0.95em; + padding: 2px; + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + box-shadow: inset 0 3px 10px #dcdcdc; + -moz-box-shadow: inset 0 3px 10px #dcdcdc; + -webkit-box-shadow: inset 0 3px 10px #dcdcdc; +} + +textarea { + font-size: 1.1em; +} + +input:focus, +input[type=submit]:hover, +input[type=reset]:hover, +textarea:focus { + border: 1px solid #e1a014; + box-shadow: inset 0 3px 10px #edd9bc; + -moz-box-shadow: inset 0 3px 10px #edd9bc; + -webkit-box-shadow: inset 0 3px 10px #edd9bc; +} + +input[type=submit], +input[type=reset] { + cursor: pointer; +} + +input[type=submit]:active, +input[type=reset]:active { + box-shadow: inset 0 3px 15px #e1a753; + -moz-box-shadow: inset 0 3px 15px #e1a753; + -webkit-box-shadow: inset 0 3px 15px #e1a753; +} + +input[disabled], +textarea[disabled] { + background-color: #f3f3f3; + border: 1px solid #989898; +} + +input:placeholder { + color: #67787c !important; +} + +input:-moz-placeholder { + color: #67787c !important; +} + +input::-webkit-input-placeholder { + color: #67787c !important; +} + +input.placeholder { + color: #67787c !important; +} + +input[type=checkbox] { + margin-top: 2px; +} + +input[type=checkbox], +input[type=radio] { + background: transparent none !important; + border: 0 none !important; +} + +.please-complete, +.please-complete:hover, +.please-complete:focus { + border: 1px #ac2525 solid !important; + box-shadow: inset 0 3px 10px #f39c9c !important; + -moz-box-shadow: inset 0 3px 10px #f39c9c !important; + -webkit-box-shadow: inset 0 3px 10px #f39c9c !important; +} + +.hidden { + display: none !important; +} + +.clear { + clear: both !important; +} diff --git a/jappixmini/jappix/css/manager.css b/jappixmini/jappix/css/manager.css new file mode 100644 index 00000000..c1dd358c --- /dev/null +++ b/jappixmini/jappix/css/manager.css @@ -0,0 +1,543 @@ +/* + +Jappix - An open social platform +This is the manager CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 31/08/11 + +*/ + +#manager { + background-color: rgb(20,20,20); + background-color: rgba(20,20,20,0.85); + width: 945px; + margin: 0 auto 25px; + padding-bottom: 17px; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-bottomright: 4px; + -webkit-border-bottom-left-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + box-shadow: 0 0 35px #5c5c5c; + -moz-box-shadow: 0 0 35px #5c5c5c; + -webkit-box-shadow: 0 0 35px #5c5c5c; +} + +#manager a { + color: black; + text-decoration: underline; +} + +#manager .clear { + clear: both; +} + +#manager fieldset { + border: 1px solid black; + margin: 22px 0 15px 0; + padding: 7px 2px 5px 2px; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; +} + +#manager legend { + font-size: 0.9em; + margin: 0 0 0 15px; + padding: 0 2px; + text-transform: uppercase; +} + +#manager label { + width: 200px; + display: block; + float: left; + clear: both; + margin: 0 0 9px 12px; +} + +#manager label.master { + text-decoration: underline; +} + +#manager input, +#manager select { + float: left; + margin-bottom: 5px; +} + +#manager input[type=radio] { + margin: 2px 8px 5px 0; +} + +#manager input[type=text], +#manager input[type=url], +#manager input[type=password], +#manager select { + margin-top: -2px; + font-size: 0.95em; +} + +#manager input[type=text], +#manager input[type=url], +#manager input[type=password] { + padding: 3px; + min-width: 220px; +} + +#manager input.icon { + padding-left: 24px; + min-width: 199px; + max-height: 18px; +} + +#manager input.icon#admin_name { + background-position: 4px -510px; +} + +#manager input.icon#admin_password, +#manager input.icon#user_repassword { + background-position: 4px -532px; +} + +#manager input.icon#user_name, +#manager input.icon#music_artist { + background-position: 4px -554px; +} + +#manager input.icon#user_password { + background-position: 4px -576px; +} + +#manager input.icon#music_title { + background-position: 4px -598px; +} + +#manager input.icon#music_album { + background-position: 4px -620px; +} + +#manager input.icon#background_image_color, +#manager input.icon#background_color_color { + background-position: 4px -641px; +} + +#manager select { + min-width: 160px; + max-width: 230px; +} + +#manager-top { + padding: 25px 45px 30px; +} + +#manager-top .logo { + background-position: 0 0; + min-width: 89px; + height: 40px; + padding: 28px 0 0 65px; + font-size: 32px; + color: white; + text-transform: lowercase; + float: left; + text-shadow: 0 1px 1px black; +} + +#manager-top .meta { + background-color: #e0eaec; + font-size: 0.9em; + padding: 12px 7px 12px 14px; + float: right; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + box-shadow: 0 0 10px #202020; + -moz-box-shadow: 0 0 10px #202020; + -webkit-box-shadow: 0 0 10px #202020; +} + +#manager-top .meta span { + margin-right: 10px; + color: black; +} + +#manager-top .meta a { + background-color: #f1f6fd; + border: 1px solid #b9cbcf; + color: #224249; + padding: 4px 8px 4px 21px; + margin-left: 2px; + text-decoration: none; + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; +} + +#manager-top .meta a:hover, +#manager-top .meta a:focus { + border: 1px solid #95b1b7; +} + +#manager-top .meta a:active { + border: 1px solid #77989f; +} + +#manager-top .meta a.logout { + background-position: 3px -69px; +} + +#manager-top .meta a.close { + background-position: 3px -90px; +} + +#manager-tabs { + margin-left: 12px; +} + +#manager-tabs a { + background-color: #d9e7ea; + color: #204249; + width: 107px; + height: 17px; + padding: 4px 4px 4px 16px; + margin-left: 4px; + font-size: 0.94em; + text-decoration: none; + overflow: hidden; + float: left; + border-top-right-radius: 3px; + border-top-left-radius: 3px; + -moz-border-radius-topright: 3px; + -moz-border-radius-topleft: 3px; + -webkit-border-top-right-radius: 3px; + -webkit-border-top-left-radius: 3px; +} + +#manager-tabs a:hover, +#manager-tabs a:focus { + background-color: #cedee1; + text-decoration: none; +} + +#manager-tabs a:active { + background-color: #c3d3d7; + text-decoration: none; +} + +#manager-tabs a.tab-active { + background-color: #e4eef9 !important; +} + +#manager-content { + background: #e4eef9; + background: -moz-linear-gradient(top, #e4eef9, #d0e5fa); + background: -webkit-gradient(linear, left top, left bottom, from(#e4eef9), to(#d0e5fa)); + color: black; + font-size: 0.9em; + margin: 0 10px; + padding: 20px 24px; + min-height: 260px; + clear: both; + right: 10px; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + box-shadow: 0 0 20px #202020; + -moz-box-shadow: 0 0 20px #202020; + -webkit-box-shadow: 0 0 20px #202020; +} + +#manager-content h3 { + padding-left: 24px; + margin-bottom: 15px; +} + +#manager-content h3.login { + background-position: 0 -466px; +} + +#manager-content h3.statistics { + background-position: 0 -203px; +} + +#manager-content h3.configuration { + background-position: 0 -224px; +} + +#manager-content h3.hosts { + background-position: 0 -246px; +} + +#manager-content h3.storage { + background-position: 0 -268px; +} + +#manager-content h3.design { + background-position: 0 -290px; +} + +#manager-content h3.users { + background-position: 0 -312px; +} + +#manager-content h3.updates { + background-position: 0 -334px; +} + +#manager-content h4 { + border-top: 1px dotted black; + padding-top: 5px; + margin: 20px 0 14px; + clear: both; +} + +#manager-content ul, +#manager-content ol { + width: 380px; + margin: 8px 0 20px 18px; +} + +#manager-content li { + margin-bottom: 3px; +} + +#manager-content li.total { + margin-bottom: 14px; +} + +#manager-content li b { + width: 190px; + float: left; +} + +#manager-content li span { + margin-left: 10px; + float: left; +} + +#manager-content ul.stats, +#manager-content ol.stats { + float: left; +} + +#manager-content object.stats { + border: 1px dotted #bed4d9; + width: 450px; + height: 270px; + margin-bottom: 20px; + float: right; +} + +#manager-content p, +#manager-content div { + margin-bottom: 10px; +} + +#manager-content .info { + color: black; + border-width: 1px; + border-style: dashed; + padding: 6px 8px; + display: block; + text-decoration: none; +} + +#manager-content .info.bottomspace { + margin-bottom: 16px; +} + +#manager-content .info.smallspace { + margin: 14px 0 10px 0; +} + +#manager-content .info.bigspace { + margin: 35px 0 20px 0; +} + +#manager-content .info.neutral { + background-color: #f0f19d; + border-color: #b3ad4f; +} + +#manager-content a.info.neutral:hover, +#manager-content a.info.neutral:focus { + background-color: #eced96; +} + +#manager-content a.info.neutral:active { + background-color: #e9ea93; +} + +#manager-content .info.success { + background-color: #a8dca9; + border-color: #5e9f5f; +} + +#manager-content a.info.success:hover, +#manager-content a.info.success:focus { + background-color: #a0d5a1; +} + +#manager-content a.info.success:active { + background-color: #9ad09b; +} + +#manager-content .info.fail { + background-color: #f19d9d; + border-color: #b34f4f; +} + +#manager-content a.info.fail:hover, +#manager-content a.info.fail:focus { + background-color: #ea9595; +} + +#manager-content a.info.fail:active { + background-color: #e59090; +} + +#manager-content .browse { + margin: 2px 0 6px; + max-height: 243px; + overflow: auto; +} + +#manager-content .browse .one-browse { + padding: 5px 10px 5px 34px; + height: 17px; +} + +#manager-content .browse .user { + background-position: 9px -111px; +} + +#manager-content .browse .other { + background-position: 9px -133px; +} + +#manager-content .browse .folder { + background-position: 9px -178px; +} + +#manager-content .browse .audio { + background-position: 9px -154px; +} + +#manager-content .browse .alert { + background-position: 9px -353px; +} + +#manager-content .browse .image { + background-position: 9px -374px; +} + +#manager-content .browse .video { + background-position: 9px -397px; +} + +#manager-content .browse .document { + background-position: 9px -418px; +} + +#manager-content .browse .package { + background-position: 9px -441px; +} + +#manager-content .browse .previous { + background-position: 9px -485px; + margin-bottom: 4px; +} + +#manager-content .browse div { + margin: 0; +} + +#manager-content .browse input { + float: right; + margin: 1px 0; +} + +#manager-content .browse .odd { + background-color: #e9f1fd; +} + +#manager-content .browse .even { + background-color: #f1f6fd; +} + +#manager-content .sub { + border-left: 1px solid black; + margin: 5px 0 20px 22px; + padding-left: 12px; + clear: both; +} + +#manager span.logo_links a { + width: 16px; + height: 16px; + margin-right: 6px; + float: left; +} + +#manager span.logo_links a.remove { + background-position: 0 -688px; +} + +#manager span.logo_links a.view { + background-position: 0 -666px; +} + +#manager-content .clear { + margin: 0; +} + +#manager-content textarea.notice-text { + height: 70px; + width: 600px; + margin-left: 4px; + padding: 5px; + font-size: 1.2em; +} + +#manager-buttons { + margin-top: 22px; +} + +#manager-buttons input { + border: 1px solid white; + background-color: rgb(255,255,255); + background-color: rgba(255,255,255,0.1); + color: white; + padding: 4px 8px; + margin-left: -12px; + margin-right: 20px; + font-size: 1em; + text-shadow: 0 1px 1px black; + float: right; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + box-shadow: 0 0 5px #202020; + -moz-box-shadow: 0 0 5px #202020; + -webkit-box-shadow: 0 0 5px #202020; +} + +#manager-buttons input:hover, +#manager-buttons input:focus { + cursor: pointer; + background-color: rgb(255,255,255); + background-color: rgba(255,255,255,0.2); + box-shadow: 0 0 15px #202020; + -moz-box-shadow: 0 0 15px #202020; + -webkit-box-shadow: 0 0 15px #202020; +} + +#manager-buttons input:active { + background-color: rgb(255,255,255); + background-color: rgba(255,255,255,0.3); +} diff --git a/jappixmini/jappix/css/me.css b/jappixmini/jappix/css/me.css new file mode 100644 index 00000000..2ff74fbd --- /dev/null +++ b/jappixmini/jappix/css/me.css @@ -0,0 +1,49 @@ +/* + +Jappix - An open social platform +This is the Jappix Me tool CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 16/01/12 + +*/ + +#me .content { + padding: 10px 0; +} + +#me .logo { + background-position: 0 0; + width: 300px; + height: 61px; + margin: 20px auto 0 auto; + display: block; +} + +#me .infos { + margin-top: 30px; +} + +#me .infos p { + margin-top: 8px; +} + +#me .infos p.infos-title { + margin-top: 0; +} + +#me .infos a { + text-decoration: underline; +} + +#me a.go { + text-align: center; + font-weight: bold; + width: 300px; + margin: 30px auto 0 auto; + padding: 8px 12px; + display: block; +} \ No newline at end of file diff --git a/jappixmini/jappix/css/mini-ie.css b/jappixmini/jappix/css/mini-ie.css new file mode 100644 index 00000000..d8f4b908 --- /dev/null +++ b/jappixmini/jappix/css/mini-ie.css @@ -0,0 +1,73 @@ +/* + +Jappix - An open social platform +This is the Jappix Mini legacy IE CSS stylesheet + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 20/03/11 + +*/ + +#jappix_mini { + width: expression(document.documentElement.clientWidth - 150 + 'px') !important; + position: absolute !important; + bottom: auto !important; + right: auto !important; + top: expression(((document.documentElement.clientHeight - this.offsetHeight) + (ignoreMiniTop = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop)) + 'px') !important; + left: expression((150 + this.offsetWidth - (document.documentElement.clientWidth ? document.documentElement.clientWidth : document.body.clientWidth) + (ignoreMiniLeft = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft)) + 'px') !important; +} + +#jappix_mini .jm_images { + background-image: url(../img/sprites/mini.gif) !important; +} + +#jappix_mini .jm_images_animate { + background-image: url(../img/sprites/animate.gif) !important; +} + +#jappix_mini a.jm_button { + width: 1px !important; +} + +#jappix_mini div.jm_roster { + right: -1px !important; + bottom: 23px !important; +} + +#jappix_mini div.jm_roster div.jm_buddies { + height: expression(this.scrollHeight > (document.documentElement.clientHeight - 70) ? (document.documentElement.clientHeight - 70) + 'px' : 'auto') !important; +} + +#jappix_mini a.jm_pane { + height: 12px !important; + overflow-y: hidden !important; +} + +#jappix_mini a.jm_button.jm_clicked { + background-image: none !important; +} + +#jappix_mini div.jm_conversations a.jm_clicked { + border-right: none !important; + padding: 7px 6px 6px 6px !important; +} + +#jappix_mini div.jm_chat-content { + bottom: 23px !important; + right: -2px !important; +} + +#jappix_popup { + height: expression(document.documentElement.clientHeight + 'px') !important; + width: expression(document.documentElement.clientWidth + 'px') !important; + position: absolute !important; + top: expression(((ignorePopupTop = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop)) + 'px') !important; + left: expression(((ignorePopupLeft = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft)) + 'px') !important; +} + +#jappix_popup div.jm_prompt { + position: absolute !important; +} diff --git a/jappixmini/jappix/css/mini.css b/jappixmini/jappix/css/mini.css new file mode 100644 index 00000000..6d77540d --- /dev/null +++ b/jappixmini/jappix/css/mini.css @@ -0,0 +1,540 @@ +/* + +Jappix - An open social platform +This is the Jappix Mini CSS stylesheet + +------------------------------------------------- + +License: AGPL +Authors: Vanaryon, Julien +Last revision: 16/01/12 + +*/ + +#jappix_mini, +#jappix_popup { + font: normal 11px helvetica, "Lucida Grande", "Lucida Sans", "Lucida Sans Unicode", Arial, sans-serif; +} + +#jappix_mini { + margin-left: 130px; + position: fixed; + bottom: 0; + right: 20px; + z-index: 999; +} + +#jappix_mini *, +#jappix_popup * { + border: none; + color: black; + width: auto; + height: auto; + margin: 0; + padding: 0; + overflow: visible; + font-size: 11px; + text-align: left; + text-transform: none; + text-shadow: none; + opacity: 1.0; + border-radius: 0; + -moz-border-radius: 0; + -webkit-border-radius: 0; + box-shadow: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; +} + +#jappix_mini .jm_images { + background-image: url(../img/sprites/mini.png); + background-repeat: no-repeat; +} + +#jappix_mini .jm_images_animate { + background-image: url(../img/sprites/animate.png); + background-repeat: no-repeat; +} + +#jappix_mini a { + text-decoration: none; + cursor: pointer; +} + +#jappix_mini a:hover { + cursor: pointer; +} + +#jappix_mini div.jm_position { + float: right; +} + +#jappix_mini a.jm_pane { + background-color: #f4f4f4; + background-position: 0 -100px; + background-repeat: repeat-x; + border-color: #999999; + border-style: solid; + border-width: 1px 1px 0 1px; + font-weight: bold; + outline-style: none; + display: block; + padding: 6px; + height: 13px; +} + +#jappix_mini a.jm_pane:hover { + background: white; +} + +#jappix_mini a.jm_pane:hover, +#jappix_mini a.jm_pane:focus { + border-color: #666666; +} + +#jappix_mini div.jm_starter, +#jappix_mini div.jm_conversations, +#jappix_mini div.jm_conversation { + float: left; + position: relative; +} + +#jappix_mini div.jm_conversation { + width: 153px; +} + +#jappix_mini a.jm_chat-tab { + border-width: 1px 0 0 1px; + width: 140px; + float: right; + overflow: hidden; +} + +#jappix_mini a.jm_chat-tab.jm_clicked { + background: white; + position: relative; + border-top: none; + border-left: 1px solid #999999; + padding-top: 7px; +} + +#jappix_mini a.jm_chat-tab span.jm_notify { + position: absolute; + top: 6px; + right: 9px; +} + +#jappix_mini a.jm_chat-tab span.jm_notify span { + float: left; +} + +#jappix_mini a.jm_chat-tab span.jm_notify span.jm_notify_left { + background-position: 0 -360px; +} + +#jappix_mini a.jm_chat-tab span.jm_notify span.jm_notify_right { + background-position: -7px -360px; +} + +#jappix_mini a.jm_chat-tab span.jm_notify span.jm_notify_left, +#jappix_mini a.jm_chat-tab span.jm_notify span.jm_notify_right { + height: 16px; + width: 7px; +} + +#jappix_mini a.jm_chat-tab span.jm_notify span.jm_notify_middle { + background-color: #c60505; + color: white; + font-size: 0.85em; + height: 14px; + padding-top: 2px; +} + +#jappix_mini div.jm_conversation.jm_type_groupchat span.jm_name { + margin-left: 4px; +} + +#jappix_mini div.jm_chat-content { + background-color: white; + border: 1px solid #999999; + height: 300px; + width: 260px; + position: absolute; + right: -1px; + bottom: 25px; + display: none; +} + +#jappix_mini div.jm_actions { + background-color: #565d5e; + border-bottom: 1px solid #3a3a3a; + height: 14px; + padding: 4px 6px; + font-weight: bold; + overflow: hidden; +} + +#jappix_mini div.jm_actions span.jm_nick { + color: white; + height: 16px; + width: 225px; + overflow: hidden; + float: left; +} + +#jappix_mini div.jm_actions a.jm_one-action { + background-color: #727879; + margin: 0 -2px 0 5px; + height: 15px; + width: 15px; + outline-style: none; + float: right; +} + +#jappix_mini div.jm_actions a.jm_one-action:hover, +#jappix_mini div.jm_actions a.jm_one-action:focus { + background-color: #7f8788; +} + +#jappix_mini div.jm_actions a.jm_one-action:active { + background-color: #8c9293; +} + +#jappix_mini div.jm_actions a.jm_logo { + background-position: 7px 2px; + width: 81px; + height: 22px; + margin: -4px 0 0 -2px; + outline-style: none; + float: left; +} + +#jappix_mini div.jm_actions a.jm_logo:hover, +#jappix_mini div.jm_actions a.jm_logo:focus { + background-color: #636a6b; +} + +#jappix_mini div.jm_actions a.jm_logo:active { + background-color: #707677; +} + +#jappix_mini div.jm_actions a.jm_close { + background-position: 1px -341px; +} + +#jappix_mini div.jm_actions a.jm_join { + background-position: 0 -327px; +} + +#jappix_mini div.jm_received-messages { + background-color: white; + padding: 5px 0 4px; + height: 242px; + overflow: auto; +} + +#jappix_mini div.jm_received-messages p { + margin: 3px 0; + word-wrap: break-word; +} + +#jappix_mini div.jm_received-messages p, +#jappix_mini div.jm_received-messages a { + color: black !important; +} + +#jappix_mini div.jm_received-messages div.jm_group { + margin: 2px 6px 9px 6px; + padding-bottom: 8px; + border-bottom: 1px solid #eaeaea; +} + +#jappix_mini div.jm_received-messages div.jm_system-message p, +#jappix_mini div.jm_received-messages div.jm_system-message a { + color: #053805 !important; + font-style: italic !important; +} + +#jappix_mini div.jm_received-messages p a { + text-decoration: underline; +} + +#jappix_mini div.jm_received-messages b { + margin-bottom: 3px; + display: block; +} + +#jappix_mini div.jm_received-messages b.jm_me { + color: #123a5c; +} + +#jappix_mini div.jm_received-messages b.jm_him { + color: #801e1e; +} + +#jappix_mini div.jm_received-messages span.jm_date { + font-size: 0.8em; + float: right; + display: none; +} + +#jappix_mini div.jm_received-messages div.jm_group:hover span.jm_date { + display: block; +} + +#jappix_mini input.jm_send-messages { + background-color: white; + border-color: #999999; + border-style: solid; + border-width: 1px 0 0 0; + padding: 5px; + width: 250px; + min-height: 14px; +} + +#jappix_mini div.jm_disabled div.jm_chat-content, +#jappix_mini div.jm_disabled input.jm_send-messages, +#jappix_mini div.jm_disabled a.jm_pane { + background: #f3f3f3 !important; +} + +#jappix_mini div.jm_disabled input.jm_send-messages { + color: #9d9d9d; +} + +#jappix_mini div.jm_roster { + background-color: white; + border: 1px solid #999999; + width: 160px; + position: absolute; + right: 0; + bottom: 25px; + display: none; +} + +#jappix_mini div.jm_roster div.jm_buddies { + width: 100%; + max-height: 300px; + padding: 5px 0; + overflow: auto; +} + +#jappix_mini div.jm_roster div.jm_grouped { + margin: 2px 0; +} + +#jappix_mini div.jm_roster div.jm_grouped div.jm_name { + margin-bottom: 2px; + padding: 4px 8px 0; + font-weight: bold; +} + +#jappix_mini a.jm_friend { + border-color: white; + border-style: solid; + border-width: 1px 0; + outline-style: none; + padding: 6px; + display: block; +} + +#jappix_mini a.jm_friend.jm_offline { + display: none; +} + +#jappix_mini a.jm_friend:hover, +#jappix_mini a.jm_friend:focus { + background-color: #888888; + border-color: #494949; + color: white; +} + +#jappix_mini a.jm_friend:hover span.jm_presence, +#jappix_mini a.jm_friend:focus span.jm_presence { + background-position: 0 -84px; +} + +#jappix_mini a.jm_button { + padding: 6px 10px; + position: relative; + z-index: 1; +} + +#jappix_mini a.jm_button.jm_clicked { + background: white; + border-top: none; + border-left: 1px solid #999999; + border-right: 1px solid #999999; + padding: 7px 10px 6px 10px; +} + +#jappix_mini span.jm_animate { + background-position: 0 0; + width: 111px; + height: 102px; + position: absolute; + top: -100px; + left: -74px; + z-index: 1; + display: block; +} + +#jappix_mini span.jm_counter { + background-position: 0 -288px; + color: #333333; + height: 16px; + padding-left: 25px; + display: block; +} + +#jappix_mini span.jm_counter.jm_error { + background-position: 0 -305px; +} + +#jappix_mini span.jm_presence { + display: block; + height: 16px; + width: 16px; + margin-right: 4px; + float: left; +} + +#jappix_mini span.jm_name { + color: #272727; + height: 14px; + width: 105px; + overflow: hidden; + float: left; +} + +#jappix_mini .jm_available, +#jappix_mini .jm_chat { + background-position: 0 -20px; +} + +#jappix_mini .jm_away { + background-position: 0 -36px; +} + +#jappix_mini .jm_xa, +#jappix_mini .jm_dnd { + background-position: 0 -52px; +} + +#jappix_mini .jm_unavailable { + background-position: 0 -68px; +} + +#jappix_mini .jm_smiley { + border: 0 none; + height: 16px; + width: 16px; + vertical-align: bottom; +} + +#jappix_mini .jm_smiley-wink { + background-position: 0 -148px; +} + +#jappix_mini .jm_smiley-waii { + background-position: 0 -164px; +} + +#jappix_mini .jm_smiley-unhappy { + background-position: 0 -180px; +} + +#jappix_mini .jm_smiley-tongue { + background-position: 0 -196px; +} + +#jappix_mini .jm_smiley-surprised { + background-position: 0 -212px; +} + +#jappix_mini .jm_smiley-smile { + background-position: 0 -228px; +} + +#jappix_mini .jm_smiley-happy { + background-position: 0 -244px; +} + +#jappix_mini .jm_smiley-grin { + background-position: 0 -260px; +} + +#jappix_popup { + background: url(../img/others/blank.gif) repeat; + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 999; +} + +#jappix_popup div.jm_prompt { + background-color: #565d5e; + border: 1px solid #3a3a3a; + width: 346px; + position: fixed; + top: 50%; + left: 50%; + margin-left: -175px; + padding: 16px 2px 2px 2px; + border-top-right-radius: 4px; + border-top-left-radius: 4px; + -moz-border-radius-topright: 4px; + -moz-border-radius-topleft: 4px; + -webkit-border-top-right-radius: 4px; + -webkit-border-top-left-radius: 4px; + box-shadow: 0 0 35px #232323; + -moz-box-shadow: 0 0 35px #232323; + -webkit-box-shadow: 0 0 35px #232323; +} + +#jappix_popup div.jm_prompt form { + background-color: white; + border: 1px solid #3a3a3a; + width: 332px; + padding: 6px; +} + +#jappix_popup div.jm_prompt form input { + background-color: #f9f9f9; + border: 1px solid #666666; + font-size: 1.1em; + padding: 1px 2px; +} + +#jappix_popup div.jm_prompt form input:hover, +#jappix_popup div.jm_prompt form input:focus { + border: 1px solid #202020; +} + +#jappix_popup div.jm_prompt form input.jm_text { + width: 326px; + margin: 6px 0; + display: block; +} + +#jappix_popup div.jm_prompt form input.jm_submit { + text-align: center; + margin-left: 3px; + float: right; +} + +#jappix_popup div.jm_prompt form input.jm_submit:hover, +#jappix_popup div.jm_prompt form input.jm_submit:focus { + background-color: #f3f3f3; + cursor: pointer; +} + +#jappix_popup div.jm_prompt form input.jm_submit:active { + background-color: #e8e8e8; +} + +#jappix_popup div.jm_prompt div.jm_clear { + clear: both; +} diff --git a/jappixmini/jappix/css/mobile.css b/jappixmini/jappix/css/mobile.css new file mode 100644 index 00000000..ed44cc03 --- /dev/null +++ b/jappixmini/jappix/css/mobile.css @@ -0,0 +1,288 @@ +/* + +Jappix - An open social platform +This is the Jappix Mobile CSS stylesheet + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 05/10/11 + +*/ + +/* BEGIN GENERAL STYLE */ + +* { + margin: 0; + padding: 0; +} + +body { + font: normal 14.4px Helvetica, Verdana, sans-serif; + background-color: #dcdcdc; + margin: 0 auto; + text-align: center; + min-width: 200px; + min-height: 260px; +} + +a { + color: black; +} + +/* END GENERAL STYLE */ + +/* BEGIN HEADER STYLE */ + +.header { + background-color: #2d2d2d; + border-bottom: 1px solid #6d6d6d; + color: #405964; + padding: 6px 0; + height: 30px; +} + +.header div { + background-position: 0 0; + width: 83px; + height: 30px; +} + +/* END HEADER STYLE */ + +/* BEGIN HOME STYLE */ + +#home .header div { + margin: 0 auto; +} + +#home .notification { + padding: 2px; + margin-top: -1px; +} + +#noscript { + background: #86a2ff; + border-bottom: 1px solid #5890d6; + color: #1e4b82; +} + +#error { + background: #ff8686; + border-bottom: 1px solid #d65858; + color: #821e1e; + display: none; +} + +#info { + background: #f3eba7; + border-bottom: 1px solid #d9d085; + color: #5e5616; + display: none; +} + +#home .login { + padding: 8px 0; + margin: 30px 0 30px 0; +} + +#home .login input { + margin-top: 5px; + padding: 2px; +} + +#home .login input.xid, +#home .login input.password { + display: block; + margin: 4px auto; + font-size: 0.85em; + padding: 4px; + background-color: white; + border: 1px solid #636363; + width: 150px; + padding-left: 24px; +} + +#home .login input.xid { + background-position: 4px -30px; +} + +#home .login input.password { + background-position: 4px -53px; +} + +#home .login label { + margin-bottom: 12px; + display: block; +} + +#home .login label input { + margin-right: 4px; +} + +#home a { + font-size: 0.8em; +} + +/* END HOME STYLE */ + +/* BEGIN TALK STYLE */ + +#talk .header div, +#chat .header div { + float: left; + margin-left: 7px; +} + +#talk .header button, +#chat .header button { + float: right; + margin-right: 7px; + padding: 2px; +} + +#talk a.one-buddy { + display: none; + background-color: #87a5ab; + border-bottom: 1px solid #5b8088; + text-shadow: 1px 1px 1px #5b8088; + text-decoration: none; + color: white; + outline-style: none; + padding: 10px 0; +} + +#talk a.one-buddy:hover { + cursor: pointer; +} + +#talk a.available, +#talk a.chat { + background-color: #83b187; + border-bottom: 1px solid #4d8252; + text-shadow: 1px 1px 1px #4d8252; +} + +#talk a.available:hover, +#talk a.chat:hover, +#talk a.available:focus, +#talk a.chat:focus { + background-color: #89c68e; +} + +#talk a.available:active, +#talk a.chat:active { + background-color: #90d496; +} + +#talk a.away { + background-color: #e0be7b; + border-bottom: 1px solid #ae8941; + text-shadow: 1px 1px 1px #ae8941; +} + +#talk a.away:hover, +#talk a.away:focus { + background-color: #eac784; +} + +#talk a.away:active { + background-color: #f3d294; +} + +#talk a.xa, +#talk a.dnd { + background-color: #db8989; + border-bottom: 1px solid #a24343; + text-shadow: 1px 1px 1px #a24343; +} + +#talk a.xa:hover, +#talk a.dnd:hover, +#talk a.xa:focus, +#talk a.dnd:focus { + background-color: #e89797; +} + +#talk a.xa:active, +#talk a.dnd:active { + background-color: #ef9f9f; +} + +/* END TALK STYLE */ + +/* BEGIN CHAT STYLE */ + +#chat { + display: none; +} + +#chat .one-chat, +#chat .one-chat p, +#chat .one-chat div, +#chat .one-chat input { + position: absolute; + bottom: 0; + right: 0; +} + +#chat .one-chat { + top: 43px; + left: 0; +} + +#chat .one-chat p { + background-color: #87a5ab; + border-bottom: 1px solid #5b8088; + text-shadow: 1px 1px 1px #5b8088; + color: white; + top: 0; + left: 0; + height: 18px; + padding: 2px 0; + font-size: 0.9em; +} + +#chat .one-chat div { + border-bottom: 1px solid #cbcbcb; + top: 23px; + left: 0; + bottom: 25px; + overflow: auto; + text-align: left; +} + +#chat .one-chat span { + display: block; + font-size: 0.85em; + margin: 4px 6px; + word-wrap: break-word; +} + +#chat .one-chat b { + margin-right: 3px; +} + +#chat .one-chat b.me { + color: #123a5c; +} + +#chat .one-chat b.him { + color: #801e1e; +} + +#chat .one-chat input { + background-color: white; + bottom: 0; + height: 25px; + width: 100%; + border: none; +} + +#chat .one-chat input.submit { + right: 0; + width: 35px; +} + +/* END CHAT STYLE */ diff --git a/jappixmini/jappix/css/mucadmin.css b/jappixmini/jappix/css/mucadmin.css new file mode 100644 index 00000000..3fe1c754 --- /dev/null +++ b/jappixmini/jappix/css/mucadmin.css @@ -0,0 +1,91 @@ +/* + +Jappix - An open social platform +This is the mucadmin CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 11/05/11 + +*/ + +#mucadmin .content { + padding: 10px 0 10px 0; +} + +#mucadmin .content p { + margin: 5px 10px 5px 10px; + text-align: justify; +} + +#mucadmin .mucadmin-head-jid { + text-decoration: underline; + font-size: 0.9em; + float: right; + margin: 1px 4px 1px 1px; +} + +#mucadmin .mucadmin-forms { + height: 310px; + width: 620px; + margin: -5px 0 0 10px; + padding: 6px 0 0 0; + overflow: auto; +} + +#mucadmin .mucadmin-forms label { + width: 260px; +} + +#mucadmin .mucadmin-topic label, +#mucadmin .mucadmin-aut label, +#mucadmin .mucadmin-others label { + font-size: 0.9em; +} + +#mucadmin .mucadmin-forms textarea { + height: 60px; + width: 300px; + margin: 5px 12px 10px 0; +} + +#mucadmin .results { + height: auto; + width: auto; + overflow: visible; + margin: 5px; +} + +#mucadmin .aut-group { + float: left; + padding-bottom: 4px; +} + +#mucadmin .one-aut { + clear: both; + margin: 0 10px 5px 0; +} + +#mucadmin .aut-add { + clear: both; + float: left; + margin-bottom: 5px; + font-size: 0.9em; +} + +#mucadmin .aut-remove { + float: left; +} + +#mucadmin .aut-remove:hover, +#mucadmin .aut-remove:focus { + font-weight: bold; + text-decoration: none; +} + +#mucadmin .mucadmin-others a { + float: left; + font-size: 0.9em; +} diff --git a/jappixmini/jappix/css/myinfos.css b/jappixmini/jappix/css/myinfos.css new file mode 100644 index 00000000..83262268 --- /dev/null +++ b/jappixmini/jappix/css/myinfos.css @@ -0,0 +1,330 @@ +/* + +Jappix - An open social platform +This is the my-infos CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 31/08/11 + +*/ + +#my-infos { + background-color: rgb(20,20,20); + background-color: rgba(20,20,20,0.85); + color: #919191; + margin-top: 8px; + padding: 15px 6px 6px 6px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0 0 6px #5c5c5c; + -moz-box-shadow: 0 0 6px #5c5c5c; + -webkit-box-shadow: 0 0 6px #5c5c5c; +} + +#my-infos .content { + background: #e8f1f3; + background: -moz-linear-gradient(top, #e4edef, #e8f1f3); + background: -webkit-gradient(linear, left top, left bottom, from(#e4edef), to(#e8f1f3)); + color: #919191; + max-height: 140px; + padding: 1px 0; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; +} + +#my-infos .element { + height: 24px; + margin: 6px 0; + position: relative; +} + +#my-infos .element .icon { + background-color: white; + border-color: #636363; + border-width: 1px; + border-style: solid; + margin-left: 6px; + height: 22px; + width: 25px; + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; +} + +#my-infos .element div.bubble a { + width: 100%; + height: 20px; +} + +#my-infos .element .icon:hover, +#my-infos .element div.bubble a:hover { + background-color: #f4f4f4; +} + +#my-infos .element .icon:active, +#my-infos .element div.bubble a:active { + background-color: #ededed; +} + +#my-infos .f-presence div.bubble a[data-value=available] { + background-position: 4px -167px; +} + +#my-infos .f-presence div.bubble a[data-value=away] { + background-position: 4px -183px; +} + +#my-infos .f-presence div.bubble a[data-value=xa] { + background-position: 4px -199px; +} + +#my-infos .f-mood div.bubble a[data-value=crazy] { + background-position: 4px -296px; +} + +#my-infos .f-mood div.bubble a[data-value=excited] { + background-position: 4px -314px; +} + +#my-infos .f-mood div.bubble a[data-value=playful] { + background-position: 4px -332px; +} + +#my-infos .f-mood div.bubble a[data-value=happy] { + background-position: 4px -350px; +} + +#my-infos .f-mood div.bubble a[data-value=shocked] { + background-position: 4px -368px; +} + +#my-infos .f-mood div.bubble a[data-value=hot] { + background-position: 4px -386px; +} + +#my-infos .f-mood div.bubble a[data-value=sad] { + background-position: 4px -404px; +} + +#my-infos .f-mood div.bubble a[data-value=amorous] { + background-position: 4px -422px; +} + +#my-infos .f-mood div.bubble a[data-value=confident] { + background-position: 4px -440px; +} + +#my-infos .f-mood a[data-value] span { + background-position: 0 -352px; +} + +#my-infos .f-mood a[data-value=crazy] span, +.mood-one { + background-position: 0 -298px; +} + +#my-infos .f-mood a[data-value=excited] span, +.mood-two { + background-position: 0 -316px; +} + +#my-infos .f-mood a[data-value=playful] span, +.mood-three { + background-position: 0 -334px; +} + +#my-infos .f-mood a[data-value=happy] span, +.mood-four { + background-position: 0 -352px; +} + +#my-infos .f-mood a[data-value=shocked] span, +.mood-five { + background-position: 0 -370px; +} + +#my-infos .f-mood a[data-value=hot] span, +.mood-six { + background-position: 0 -388px; +} + +#my-infos .f-mood a[data-value=sad] span, +.mood-seven { + background-position: 0 -406px; +} + +#my-infos .f-mood a[data-value=amorous] span, +.mood-eight { + background-position: 0 -424px; +} + +#my-infos .f-mood a[data-value=confident] span, +.mood-nine { + background-position: 0 -442px; +} + +#my-infos .f-activity div.bubble a[data-value=doing_chores] { + background-position: 4px -458px; +} + +#my-infos .f-activity div.bubble a[data-value=drinking] { + background-position: 4px -476px; +} + +#my-infos .f-activity div.bubble a[data-value=eating] { + background-position: 4px -494px; +} + +#my-infos .f-activity div.bubble a[data-value=exercising] { + background-position: 4px -512px; +} + +#my-infos .f-activity div.bubble a[data-value=grooming] { + background-position: 4px -548px; +} + +#my-infos .f-activity div.bubble a[data-value=having_appointment] { + background-position: 4px -566px; +} + +#my-infos .f-activity div.bubble a[data-value=inactive] { + background-position: 4px -530px; +} + +#my-infos .f-activity div.bubble a[data-value=relaxing] { + background-position: 4px -620px; +} + +#my-infos .f-activity div.bubble a[data-value=talking] { + background-position: 4px -602px; +} + +#my-infos .f-activity div.bubble a[data-value=traveling] { + background-position: 4px -584px; +} + +#my-infos .f-activity div.bubble a[data-value=working] { + background-position: 4px -638px; +} + +#my-infos .f-activity a[data-value] span { + background-position: 0 -514px; +} + +#my-infos .f-activity a[data-value=doing_chores] span, +.activity-doing_chores { + background-position: 0 -460px; +} + +#my-infos .f-activity a[data-value=drinking] span, +.activity-drinking { + background-position: 0 -478px; +} + +#my-infos .f-activity a[data-value=eating] span, +.activity-eating { + background-position: 0 -496px; +} + +#my-infos .f-activity a[data-value=exercising] span, +.activity-exercising { + background-position: 0 -514px; +} + +#my-infos .f-activity a[data-value=grooming] span, +.activity-grooming { + background-position: 0 -550px; +} + +#my-infos .f-activity a[data-value=having_appointment] span, +.activity-having_appointment { + background-position: 0 -568px; +} + +#my-infos .f-activity a[data-value=inactive] span, +.activity-inactive { + background-position: 0 -532px; +} + +#my-infos .f-activity a[data-value=relaxing] span, +.activity-relaxing { + background-position: 0 -622px; +} + +#my-infos .f-activity a[data-value=talking] span, +.activity-talking { + background-position: 0 -604px; +} + +#my-infos .f-activity a[data-value=traveling] span, +.activity-traveling { + background-position: 0 -586px; +} + +#my-infos .f-activity a[data-value=working] span, +.activity-working { + background-position: 0 -640px; +} + +#my-infos .element .icon.picker { + border-width: 1px 0 1px 1px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + -moz-border-radius-topright: 0; + -moz-border-radius-bottomright: 0; + -webkit-border-top-right-radius: 0; + -webkit-border-bottom-right-radius: 0; +} + +#my-infos .element .icon.disabled { + background-color: #f3f3f3; + border-color: #989898; + cursor: default; +} + +#my-infos .element div.bubble { + background-color: white; + border-color: #636363; + border-width: 1px 1px 0 1px; + border-style: solid; + width: 25px; + padding: 1px 0; + position: absolute; + bottom: 21px; + left: 6px; + border-top-left-radius: 2px; + border-top-right-radius: 2px; + -moz-border-radius-topleft: 2px; + -moz-border-radius-topright: 2px; + -webkit-border-top-left-radius: 2px; + -webkit-border-top-right-radius: 2px; +} + +#my-infos .element a { + float: left; +} + +#my-infos .element .icon span { + height: 16px; + width: 16px; + margin: 3px 4px; + display: block; +} + +#my-infos .element input { + height: 18px; + width: 190px; + font-size: 0.85em; + padding-left: 4px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + -moz-border-radius-topleft: 0; + -moz-border-radius-bottomleft: 0; + -webkit-border-top-left-radius: 0; + -webkit-border-top-bottom-radius: 0; +} diff --git a/jappixmini/jappix/css/options.css b/jappixmini/jappix/css/options.css new file mode 100644 index 00000000..6dc4ecdb --- /dev/null +++ b/jappixmini/jappix/css/options.css @@ -0,0 +1,97 @@ +/* + +Jappix - An open social platform +This is the options CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 26/04/11 + +*/ + +#options label { + width: 190px; + font-size: 0.94em; +} + +#options .forms { + width: 610px; + height: 328px; + margin: 15px 15px 15px 15px; + float: left; +} + +#options .forms select { + margin-top: -3px; + min-width: 120px; + float: left; +} + +#options .forms a.linked { + font-size: 0.9em; + float: left; +} + +#options fieldset.privacy { + display: none; +} + +#options .sub-ask-delete, #options .sub-ask-pass, #options .sub-ask-pass-success { + display: none; +} + +#options .sub-ask { + display: none; + width: 592px; + padding: 8px; + float: left; + border: 1px #879da2 dotted; + background: #cbdde1; +} + +#options .sub-ask-top { + font-size: 0.9em; + margin-top: -3px; +} + +#options .sub-ask-title { + margin-bottom: 4px; + float: left; +} + +#options .sub-ask-close { + float: right; +} + +#options .sub-ask-close:hover { + cursor: pointer; +} + +#options .sub-ask-content { + clear: both; + height: 25px; + font-size: 0.9em; + border-top: 1px #416972 solid; + padding: 12px 0; +} + +#options .sub-ask-content label { + width: 125px; +} + +#options .sub-ask-content input { + width: 125px; +} + +#options .sub-ask-bottom { + clear: both; + font-size: 0.9em; + float: right; + text-decoration: underline; +} + +#options .sub-ask-bottom:hover { + cursor: pointer; +} diff --git a/jappixmini/jappix/css/others.css b/jappixmini/jappix/css/others.css new file mode 100644 index 00000000..f3e2461f --- /dev/null +++ b/jappixmini/jappix/css/others.css @@ -0,0 +1,118 @@ +/* + +Jappix - An open social platform +This is the others CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 31/08/11 + +*/ + +#audio { + display: none; +} + +#top-content { + position: absolute; + right: 5px; + left: 5px; + top: 0; + min-width: 860px; + z-index: 50; +} + +#main-content { + position: absolute; + top: 34px; + left: 5px; + right: 5px; + bottom: 5px; + min-width: 850px; + min-height: 450px; +} + +#left-content { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 248px; +} + +#right-content { + background-color: rgb(20,20,20); + background-color: rgba(20,20,20,0.85); + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 260px; + z-index: 10; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0 0 6px #5c5c5c; + -moz-box-shadow: 0 0 6px #5c5c5c; + -webkit-box-shadow: 0 0 6px #5c5c5c; +} + +#general-wait { + background: url(../img/others/blank.gif) repeat; + z-index: 10000; + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; +} + +.general-wait-content { + background-color: rgb(255,255,255); + background-color: rgba(255,255,255,0.9); + background-position: 8px 8px; + padding: 8px; + position: absolute; + right: 5px; + bottom: 5px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0 0 2px #000; + -moz-box-shadow: 0 0 2px #000; + -webkit-box-shadow: 0 0 2px #000; +} + +.inbox-hidable, +.options-hidable, +.pep-hidable, +.pubsub-hidable, +.archives-hidable, +.commands-hidable, +.privacy-hidable, +.xmpplinks-hidable { + display: none; +} + +#reconnect .pane { + background-color: rgb(20,20,20); + background-color: rgba(20,20,20,0.85); + color: white; + padding: 25px; + z-index: 10000; + text-shadow: 0 1px 1px black; + position: absolute; + left: 0; + right: 0; + top: 0; + box-shadow: 0 0 35px #232323; + -moz-box-shadow: 0 0 35px #232323; + -webkit-box-shadow: 0 0 35px #232323; +} + +#reconnect .pane a { + margin-top: -4px; + float: right; +} diff --git a/jappixmini/jappix/css/pageengine.css b/jappixmini/jappix/css/pageengine.css new file mode 100644 index 00000000..75038b6c --- /dev/null +++ b/jappixmini/jappix/css/pageengine.css @@ -0,0 +1,601 @@ +/* + +Jappix - An open social platform +This is the page-engine CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 31/08/11 + +*/ + +#page-engine { + background-color: #f4f4f4; + position: absolute; + top: 40px; + bottom: 6px; + right: 6px; + left: 6px; + z-index: 8; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; +} + +#page-engine .top { + background: #e8f1f3; + background: -moz-linear-gradient(top, #e8f1f3, #dee8ea); + background: -webkit-gradient(linear, left top, left bottom, from(#e8f1f3), to(#dee8ea)); + border-bottom: 1px solid #d0d0d0; + color: black; + position: absolute; + top: 0; + left: 0; + right: 0; + padding: 6px; + height: 80px; + border-top-right-radius: 3px; + border-top-left-radius: 3px; + -moz-border-radius-topright: 3px; + -moz-border-radius-topleft: 3px; + -webkit-border-top-right-radius: 3px; + -webkit-border-top-left-radius: 3px; +} + +#page-engine .top .avatar-container { + text-align: center; + margin: 2px 0 0 10px; + height: 76px; + width: 76px; + float: left; +} + +#page-engine .top .avatar { + max-height: 76px; + max-width: 76px; +} + +#page-engine .top .name { + text-align: right; + padding: 7px; +} + +#page-engine p.bc-name { + font-size: 2.3em; + margin-bottom: 5px; +} + +#page-engine p.bc-infos { + font-size: 0.85em; + height: 16px; + overflow: hidden; + position: absolute; + left: 115px; + right: 12px; +} + +#page-engine .page-engine-chan[data-type=groupchat] p.bc-infos { + left: 12px; +} + +#page-engine p.bc-infos span.show { + padding-left: 18px; +} + +#page-engine p.bc-infos a { + text-decoration: underline; +} + +#page-engine div.bc-pep { + float: right; +} + +#page-engine div.bc-pep a { + height: 16px; + width: 16px; + margin-left: 4px; + float: left; +} + +#page-engine div.bc-pep a:hover { + cursor: default; +} + +#page-engine div.bc-pep a[href]:hover { + cursor: pointer; +} + +#page-engine .content, +#page-engine .list { + font-size: 0.9em; + position: absolute; + top: 93px; + right: 0; + bottom: 29px; + overflow: auto; + box-shadow: inset 0 3px 10px #e8e8e8; + -moz-box-shadow: inset 0 3px 10px #e8e8e8; + -webkit-box-shadow: inset 0 3px 10px #e8e8e8; +} + +#page-engine .content { + left: 0; + padding: 12px 14px 0; +} + +#page-engine .content a { + text-decoration: underline; +} + +#page-engine .page-engine-chan { + display: none; +} + +#page-engine .chat .content, +#page-engine .chat .list { + bottom: 93px; +} + +#page-engine .chat .content { + padding-bottom: 24px; +} + +#page-engine .groupchat-content { + padding-bottom: 16px !important; + right: 191px !important; +} + +#page-engine .list { + border-left: 1px solid #c8c8c8; + padding: 8px 0 0; + width: 190px; + right: 0; +} + +#page-engine .list .role { + display: none; + margin-bottom: 10px; +} + +#page-engine .list .title { + font-weight: bold; + color: #383838; + margin-left: 8px; +} + +#page-engine .list .user { + background: #eff2f2; + background: -moz-linear-gradient(top, #eff2f2, #ecefef); + background: -webkit-gradient(linear, left top, left bottom, from(#eff2f2), to(#ecefef)); + border-color: #c8c8c8; + border-width: 1px 0; + border-style: solid; + color: #383838; + margin-bottom: 3px; + height: 32px; + overflow: hidden; +} + +#page-engine .list .user:hover { + background: #e9ecec; + cursor: pointer; +} + +#page-engine .list .user:active { + background: #e3e7e7; +} + +#page-engine .list .user.myself { + background-color: #eff2f2; + cursor: default; +} + +#page-engine .list .user .name { + float: left; + height: 18px; + overflow: hidden; + margin: 7px 0 7px 3px; + padding-left: 18px; +} + +#page-engine .list .user .avatar-container { + text-align: center; + float: right; + height: 32px; + width: 32px; +} + +#page-engine .list .user .avatar { + max-height: 32px; + max-width: 32px; +} + +#page-engine .one-group, +#archives .one-group { + border-bottom: 1px dotted #d0d0d0; + padding-bottom: 8px; + margin-bottom: 10px; +} + +#page-engine .one-line, +#archives .one-line, +#page-engine .one-group b.name, +#archives .one-group b.name { + padding-left: 50px; + word-wrap: break-word; +} + +#page-engine .one-group b.name, +#archives .one-group b.name { + display: block; + margin-bottom: 4px; +} + +#page-engine .one-group b.name.me, +#archives .one-group b.name.me { + color: #123a5c; +} + +#page-engine .one-group b.name.him, +#archives .one-group b.name.him { + color: #801e1e; +} + +#page-engine .one-group span.date, +#archives .one-group span.date { + float: right; + font-size: 0.9em; +} + +#page-engine .one-group .avatar-container, +#archives .one-group .avatar-container { + text-align: center; + margin: 4px 0 0 6px; + height: 30px; + width: 30px; + float: left; +} + +#page-engine .one-group .avatar, +#archives .one-group .avatar { + max-height: 30px; + max-width: 30px; +} + +#page-engine b.name.talk-images { + background-position: 50px -99px; + padding-left: 68px; +} + +#page-engine .user-message, +#archives .user-message { + margin-bottom: 3px; +} + +#page-engine .system-message { + color: #053805 !important; + margin-bottom: 3px !important; + padding-left: 0 !important; +} + +#page-engine .system-message a { + color: #053805 !important; +} + +#page-engine .system-message p.help b { + margin-bottom: 5px; + text-decoration: underline; + display: block; +} + +#page-engine .system-message p.help em { + width: 240px; + text-decoration: underline; + margin-left: 5px; + float: left; +} + +#page-engine .my-nick { + font-weight: bold; +} + +#page-engine .old-message { + font-size: 11px !important; + margin-bottom: 1px !important; +} + +#page-engine .chatstate { + background-color: rgb(234,234,234); + background-color: rgba(234,234,234,0.8); + color: #2c2c2c; + padding: 3px 10px 2px 8px; + position: absolute; + bottom: 93px; + left: 0; + font-size: 0.75em; + border-top-right-radius: 3px; + -moz-border-radius-topright: 3px; + -webkit-border-top-right-radius: 3px; +} + +#page-engine .text { + height: 93px; + position: absolute; + bottom: 0; + left: 0; + right: 0; +} + +#page-engine .footer { + background: #e8f1f3; + background: -moz-linear-gradient(top, #dee8ea, #e8f1f3); + background: -webkit-gradient(linear, left top, left bottom, from(#dee8ea), to(#e8f1f3)); + border-color: #d0d0d0; + border-width: 1px 0 0; + border-style: solid; + color: black; + position: absolute; + left: 0; + right: 0; + padding: 6px; + height: 16px; +} + +#page-engine .chat .footer { + border-width: 1px 0; + position: static; +} + +#page-engine .chat-tools-content { + height: 16px; + width: 16px; + margin-right: 8px; + float: left; +} + +#page-engine .tools-tooltip { + display: block; + height: 16px; + width: 16px; + overflow: hidden; + float: left; +} + +#page-engine .text .chat-tools-smileys { + margin-left: 4px; +} + +#page-engine .text .chat-tools-file { + display: none; +} + +#page-engine .text .chat-tools-file.mini .bubble-file { + z-index: 39; +} + +#page-engine .text .chat-tools-file.mini .tooltip-subitem { + width: 22px; + height: 20px; +} + +#page-engine .text .chat-tools-file.mini .wait { + margin: -2px 0 0 -1px; +} + +#page-engine .text .tools-smileys { + background-position: 0 -388px; +} + +#page-engine .text .tools-style { + background-position: 0 -700px; +} + +#page-engine .text .tools-file { + background-position: 0 -1956px; +} + +#page-engine .text .tools-save { + background-position: 0 -719px; +} + +#page-engine .text .tools-clear { + background-position: 0 -739px; +} + +#page-engine .text .tools-infos, +#channel .top div.shortcuts a.profile { + background-position: 0 -758px; +} + +#page-engine .text .tools-add, +#page-engine .text .tools-archives, +#page-engine .text .tools-mucadmin { + display: none; +} + +#page-engine .text .tools-mucadmin { + background-position: 0 -777px; +} + +#page-engine .bubble-style label.bold { + font-weight: bold; +} + +#page-engine .bubble-style label.italic { + font-style: italic; +} + +#page-engine .bubble-style label.underline { + text-decoration: underline; +} + +#page-engine .bubble-style a.color { + height: 18px; + width: 18px; + border-color: white; + border-width: 1px; + border-style: solid; + margin: 6px 5px 0 0; + float: left; + opacity: 0.6; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + box-shadow: 0 0 5px black; + -moz-box-shadow: 0 0 5px black; + -webkit-box-shadow: 0 0 5px black; +} + +#page-engine .bubble-style a.color:hover, +#page-engine .bubble-style a.color:focus { + opacity: 0.7; +} + +#page-engine .bubble-style a.color.selected { + opacity: 1; + border-color: #ffb20d; +} + +#page-engine .bubble-file .tooltip-subitem { + width: 240px; +} + +#page-engine .bubble-file input[type=file] { + width: 220px; +} + +#page-engine .bubble-file input[type=submit], +#page-engine .bubble-file input[type=reset] { + margin: 4px 4px 0 0; +} + +#page-engine .text .compose, +#page-engine .muc-ask { + position: absolute; + left: 0; +} + +#page-engine .text .compose { + top: 29px; + right: 12px; + bottom: 12px; +} + +#page-engine .muc-ask { + right: 0; + bottom: 0; +} + +#page-engine .text textarea { + border: 1px solid #c8c8c8; + padding: 5px; + height: 100%; + width: 100%; + font-size: 1.1em; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-top-right-radius: 0; + border-top-left-radius: 0; + -moz-border-radius-topright: 0; + -moz-border-radius-topleft: 0; + -webkit-border-top-right-radius: 0; + -webkit-border-top-left-radius: 0; +} + +#page-engine .muc-ask { + background-color: #e8f1f3; + height: 64px; + font-size: 0.9em; + right: 0; + z-index: 2; + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; + -moz-border-radius-bottomleft: 3px; + -moz-border-radius-bottomright: 3px; + -webkit-border-bottom-left-radius: 3px; + -webkit-border-bottom-right-radius: 3px; +} + +#page-engine .muc-ask label { + color: #224249; + margin: 23px 10px 0 16px; + font-weight: bold; +} + +#page-engine .muc-ask input { + width: 200px; + margin: 19px 10px 0 10px; + padding: 3px; +} + +#page-engine .tooltip { + position: absolute; + bottom: 84px; + margin-left: -13px; + z-index: 40; + font-size: 0.8em; + color: white; +} + +#page-engine .tooltip a { + color: white; + text-decoration: underline; +} + +#page-engine .tooltip-subitem { + background-color: rgb(0,0,0); + background-color: rgba(0,0,0,0.8); + padding: 10px; + width: 200px; + height: 110px; + text-shadow: 0 1px 1px black; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; +} + +#page-engine .tooltip-subarrow { + background-position: 0 -251px; + opacity: 0.8; + height: 10px; + width: 18px; + margin-left: 12px; +} + +#page-engine .tooltip .tooltip-top { + margin-bottom: 8px; + font-weight: bold; +} + +#page-engine .tooltip label { + margin-bottom: 4px; + float: left; + clear: both; +} + +#page-engine .tooltip label input[type=checkbox] { + margin: 0 6px 0 0; + float: left; +} + +#page-engine .tooltip label.select { + margin-top: 5px; +} + +#page-engine .tooltip input, +#page-engine .tooltip select { + float: left; +} + +#page-engine .tooltip select { + width: 100px; +} + +#page-engine .tooltip .tooltip-actionlog:hover, +#page-engine .tooltip .tooltip-actionlog:focus { + cursor: pointer; + text-decoration: underline; +} diff --git a/jappixmini/jappix/css/pageswitch.css b/jappixmini/jappix/css/pageswitch.css new file mode 100644 index 00000000..59830049 --- /dev/null +++ b/jappixmini/jappix/css/pageswitch.css @@ -0,0 +1,209 @@ +/* + +Jappix - An open social platform +This is the page-switch CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 22/05/11 + +*/ + +#page-switch { + position: absolute; + top: 15px; + left: 10px; + right: 10px; + z-index: 9; +} + +#page-switch .chans { + position: absolute; + left: 0; + right: 40px; + top: 0; + height: 25px; + overflow: hidden; +} + +#page-switch .more { + position: absolute; + right: 0; + top: 0; +} + +#page-switch .more-button { + background-position: 6px -1372px; + background-color: #d9e7ea; + width: 7px; + height: 17px; + padding: 1px 6px; + font-size: 0.9em; + text-decoration: none; + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; +} + +#page-switch .more-content { + background-color: #d9e7ea; + width: 200px; + max-height: 400px; + overflow: auto; + position: absolute; + margin: -2px 0 0 -181px; + padding: 4px 0; + font-size: 0.95em; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; + -moz-border-radius-topleft: 3px; + -moz-border-radius-bottomleft: 3px; + -moz-border-radius-bottomright: 3px; + -webkit-border-top-left-radius: 3px; + -webkit-border-bottom-left-radius: 3px; + -webkit-border-bottom-right-radius: 3px; +} + +#page-switch .switcher { + background-color: #d9e7ea; + color: #17353b; + height: 15px; + padding: 5px 10px 5px 5px; + margin: 0 2px; + font-size: 0.85em; + float: left; + border-top-right-radius: 3px; + border-top-left-radius: 3px; + -moz-border-radius-topright: 3px; + -moz-border-radius-topleft: 3px; + -webkit-border-top-right-radius: 3px; + -webkit-border-top-left-radius: 3px; +} + +#page-switch .more-content .switcher { + background-color: #d9e7ea; + float: none; + margin: 0; + font-size: 0.9em; + border-radius: 0; + -moz-border-radius: 0; + -webkit-border-radius: 0; +} + +#page-switch .more-content .switcher .exit { + display: block; +} + +#page-switch .more-button:hover, +#page-switch .more-button:focus, +#page-switch .switcher:hover, +#page-switch .switcher:focus, +#page-switch .more-content .switcher:hover, +#page-switch .more-content .switcher:focus { + background-color: #cedee1; + cursor: pointer; +} + +#page-switch .more-button:active, +#page-switch .switcher:active, +#page-switch .more-content .switcher:active { + background-color: #c3d3d7; +} + +#page-switch .switcher.activechan { + background-color: #e8f1f3; +} + +#page-switch .more-content .switcher.activechan { + background-color: #d1e0e3; +} + +#page-switch .icon { + height: 16px; + width: 16px; + float: left; +} + +#page-switch .name { + float: left; + margin-left: 4px; + max-height: 16px; + max-width: 140px; + overflow: hidden; +} + +#page-switch .exit { + display: none; + background-color: #bdd9dc; + border: 1px solid #80aab0; + color: #355e64; + height: 14px; + width: 13px; + margin-left: 10px; + font-size: 0.85em; + text-align: center; + text-decoration: none; + float: right; + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; +} + +#page-switch .exit:hover, +#page-switch .exit:focus { + background-color: #aac7cb; +} + +#page-switch .exit:active { + background-color: #9bbdc1; +} + +#page-switch .activechan .exit { + display: block; + float: right; +} + +#page-switch .chan-newmessage { + background-color: #f6edc3 !important; +} + +#page-switch .chan-newmessage:hover, +#page-switch .chan-newmessage:focus { + background-color: #f1eac0 !important; +} + +#page-switch .chan-newmessage:active { + background-color: #ede4b8 !important; +} + +#page-switch .composing, +#page-engine .list .user.composing { + color: #217021 !important; +} + +#page-switch .paused, +#page-switch .chan-unread .name, +#page-engine .list .user.paused { + color: #2431ac !important; +} + +#page-switch .active, +#page-engine .list .user.active { + color: #353535 !important; +} + +#page-switch .inactive, +#page-engine .list .user.inactive { + color: #585858 !important; +} + +#page-switch .gone { + color: #851313 !important; +} + +#page-switch .channel .icon { + background-position: 0 -55px; +} diff --git a/jappixmini/jappix/css/popup.css b/jappixmini/jappix/css/popup.css new file mode 100644 index 00000000..e5b3eb92 --- /dev/null +++ b/jappixmini/jappix/css/popup.css @@ -0,0 +1,612 @@ +/* + +Jappix - An open social platform +This is the popup CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 02/05/11 + +*/ + +.lock { + background-color: rgb(0,0,0); + background-color: rgba(0,0,0,0.6); + left: 0; + right: 0; + top: 0; + bottom: 0; + position: fixed; + z-index: 9999; +} + +.popup { + background-color: rgb(20,20,20); + background-color: rgba(20,20,20,0.95); + margin-top: -250px; + margin-left: -330px; + width: 640px; + height: 500px; + padding: 0 10px; + position: absolute; + z-index: 10000; + left: 50%; + top: 50%; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + box-shadow: 0 0 35px #232323; + -moz-box-shadow: 0 0 35px #232323; + -webkit-box-shadow: 0 0 35px #232323; +} + +.popup.large { + margin-left: -460px; + width: 920px; +} + +.popup .top { + width: 600px; + height: 45px; + font-size: 1.2em; + padding-top: 9px; + color: white; + margin: 14px 0 0 40px; + text-transform: uppercase; + text-decoration: none; + font-weight: bold; + text-shadow: 0 2px 2px black; +} + +.popup .tab { + width: 620px; + height: 25px; + margin: -5px 10px 0 10px; + position: relative; + z-index: 1; +} + +.popup .tab a { + background-color: #d9e7ea; + color: #204249; + width: 180px; + height: 17px; + padding: 4px 4px 4px 16px; + margin-left: 5px; + font-size: 0.94em; + overflow: hidden; + float: left; + border-top-right-radius: 3px; + border-top-left-radius: 3px; + -moz-border-radius-topright: 3px; + -moz-border-radius-topleft: 3px; + -webkit-border-top-right-radius: 3px; + -webkit-border-top-left-radius: 3px; +} + +.popup .tab a:hover, +.popup .tab a:focus { + background-color: #cedee1; + text-decoration: none; +} + +.popup .tab a:active { + background-color: #c3d3d7; + text-decoration: none; +} + +.popup .tab a.tab-active { + background-color: #e4eef9 !important; +} + +.popup .one-lap { + display: none; +} + +.popup .one-lap.lap-active { + display: block; +} + +.popup .content { + background: #e4eef9; + background: -moz-linear-gradient(top, #e4eef9, #D0E5FA); + background: -webkit-gradient(linear, left top, left bottom, from(#e4eef9), to(#D0E5FA)); + height: 358px; + width: 640px; + position: absolute; + left: 10px; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + box-shadow: 0 0 20px black; + -moz-box-shadow: 0 0 20px black; + -webkit-box-shadow: 0 0 20px black; +} + +.popup .content, +.popup .content a { + color: #112a2f; +} + +.popup.large div.comments { + background-color: #f4f4f4; + width: 272px; + margin: 0; + position: absolute; + right: 10px; + top: 63px; + bottom: 10px; + overflow-x: hidden; + overflow-y: auto; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; +} + +.popup.large div.comments div.comments-content { + font-size: 0.8em; +} + +.popup.large div.comments input { + width: 185px; + min-width: 0; +} + +.popup.large div.comments .one-comment { + padding-bottom: 4px; +} + +.popup.large div.comments .one-comment a { + text-decoration: underline; +} + +.popup.large div.comments div.comments-content { + border-top-right-radius: 3px; + border-top-left-radius: 3px; + -moz-border-radius-topright: 3px; + -moz-border-radius-topleft: 3px; + -webkit-border-top-right-radius: 3px; + -webkit-border-top-left-radius: 3px; +} + +.popup .head { + background: #f1f6fd; + border: 1px #9dc4fc solid; + width: 606px; + height: 24px; + margin: 0 10px 10px 10px; + padding: 6px; +} + +.popup .head-text { + float: left; + font-size: 0.9em; + margin: 3px; +} + +.popup .head-actions { + float: right; + margin-top: 2px; +} + +.popup .head-actions a { + font-size: 0.9em; + margin: 0 4px; +} + +.popup .actions a { + color: #30575f; + font-size: 0.9em; + margin-left: 5px; +} + +.popup .head .head-input { + float: right; + width: 200px; + padding: 2px; +} + +.popup .head .head-select { + float: right; + height: 24px; +} + +.popup .forms { + width: 390px; + height: 328px; + margin: 15px; + float: left; +} + +.popup fieldset { + border: 1px #547177 solid; + margin: 0 0 15px 0; + padding: 8px 2px 3px 2px; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; +} + +.popup legend { + font-size: 0.9em; + margin: 0 0 0 15px; + padding: 0 2px; + text-transform: uppercase; +} + +.popup label { + color: #1b1b1b; + width: 150px; + margin: 0 0 10px 12px; + clear: both; + float: left; +} + +.popup input, +.popup select { + margin: 0 10px 10px 0; + float: left; +} + +.popup input[type=text] { + min-width: 174px; +} + +.popup select { + min-height: 20px; +} + +.popup .results { + height: 310px; + width: 620px; + margin: -5px 0 0 10px; + padding: 6px 0 0 0; + overflow: auto; +} + +.popup .results .no-results { + margin: 6px 0; + font-size: 0.85em; + font-weight: bold; +} + +.popup .results label { + width: 180px; + margin: 6px 4px 4px 4px; +} + +.popup .results input, +.popup .results textarea, +.popup .results select { + margin: 4px; +} + +.popup .results input, +.popup .results select { + min-width: 180px; +} + +.popup .results input[type=checkbox], +.popup .results input[type=radio] { + margin-top: 7px; +} + +.popup .results textarea { + width: 380px; +} + +.popup .results .avatar-container { + float: left; + width: 60px; + height: 60px; + margin: 5px 12px 5px 9px; + text-align: center; + background-repeat: no-repeat; +} + +.popup .results img.avatar { + max-width: 60px; + max-height: 60px; +} + +.popup .results .one-icon { + height: 16px; + width: 16px; + margin: 10px 3px 0 8px; + float: left; +} + +.popup .results .one-icon.account, +.popup .results .one-icon.auth { + background-position: 0 -777px; +} + +.popup .results .one-icon.automation, +.popup .results .one-icon.client { + background-position: 0 -1500px; +} + +.popup .results .one-icon.collaboration { + background-position: 0 -1520px; +} + +.popup .results .one-icon.proxy, +.popup .results .one-icon.server, +.popup .results .one-icon.others { + background-position: 0 -1540px; +} + +.popup .results .one-icon.component, +.popup .results .one-icon.gateway { + background-position: 0 -1560px; +} + +.popup .results .one-icon.conference { + background-position: 0 -1082px; +} + +.popup .results .one-icon.directory { + background-position: 0 -876px; +} + +.popup .results .one-icon.headline, +.popup .results .one-icon.hierarchy { + background-position: 0 -1580px; +} + +.popup .results .one-icon.pubsub, +.popup .results .one-icon.store { + background-position: 0 -1600px; +} + +.popup .results .one-icon.loading { + background-position: 0 -1620px; +} + +.popup .results .one-icon.down { + background-position: 0 -1640px; +} + +.popup .results .one-host { + font-weight: bold; + width: 170px; +} + +.popup .results .one-type { + width: 210px; +} + +.popup .results .one-type, +.popup .results .one-host { + float: left; + overflow: hidden; + margin: 9px 8px; +} + +.popup .results .one-jid, +.popup .results .one-ctry, +.popup .results .one-fn { + margin: 4px; + width: 400px; +} + +.popup .results .one-fn { + font-weight: bold; +} + +.popup .results .one-jid { + margin-top: 8px; + font-size: 0.9em; +} + +.popup .results .one-name { + float: left; + margin: 4px; +} + +.popup a.one-button { + display: none; + background-color: #f1f6fd; + border: 1px solid #b9cbcf; + margin-top: 1px; + padding: 4px 8px; + text-decoration: none; + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; +} + +.popup a.one-button:hover, +.popup a.one-button:focus { + border: 1px solid #95b1b7; +} + +.popup a.one-button:active { + border: 1px solid #77989f; +} + +.popup .results .oneresult:hover a.one-button { + display: block; +} + +.popup .results a.one-button, +#inbox .one-message a.one-button { + float: right; + padding: 3px 6px; + margin-right: 4px; +} + +.popup .results a.one-vjud { + position: absolute; + right: 4px; +} + +.popup .results a.one-add { + top: 8px; +} + +.popup .results a.one-chat { + top: 42px; +} + +.popup .results .one-next { + float: right; + margin: 4px 8px 4px 4px; + font-weight: bold; +} + +.popup .results .one-actions { + width: 148px; + margin: 4px; + float: left; +} + +.popup .results .one-actions a { + width: 16px; + height: 16px; + padding: 2px 2px 4px 5px !important; + margin-top: 2px; +} + +.popup .results .one-actions a.browse { + background-position: 3px -1396px; +} + +.popup .results .one-actions a.command { + background-position: 3px -1415px; +} + +.popup .results .one-actions a.subscribe { + background-position: 4px -1435px; +} + +.popup .results .one-actions a.join { + background-position: 3px -1455px; +} + +.popup .results .one-actions a.search { + background-position: 4px -1475px; +} + +.popup .results a.submit, +.popup .results a.cancel, +.popup .results a.back { + margin-right: 8px; + float: right; +} + +.popup .onetitle { + font-size: 0.9em; + padding: 4px; + font-weight: bold; +} + +.popup .oneinstructions { + font-size: 0.9em; + padding: 4px; + margin: 8px 0; +} + +.popup .oneresult { + font-size: 0.9em; + padding: 3px 0 4px 4px; + border-bottom: 1px #9dc4fc solid; + overflow: hidden; + position: relative; +} + +.popup .oneresult:hover { + background: #e9f1fd; +} + +.popup .oneresult[onclick]:hover { + cursor: pointer; +} + +.popup .oneresult[onclick]:active { + background: #f1f6fd; +} + +.popup .infos { + background-color: rgb(255,239,104); + background-color: rgba(255,239,104,0.8); + border: 1px #decb2f solid; + color: #3f3f3f; + padding: 8px; + margin: 10px; + font-size: 0.8em; +} + +.popup .infos p { + margin-top: 10px; +} + +.popup .infos p.infos-title { + font-weight: bold; +} + +.popup .bottom { + width: 640px; + height: 40px; + position: absolute; + bottom: 8px; +} + +.popup .wait { + display: none; + margin: 8px 0 0 3px; + float: left; +} + +a.finish { + border: 1px solid white; + background-color: rgb(255,255,255); + background-color: rgba(255,255,255,0.1); + color: white; + padding: 4px 8px; + margin-right: 7px; + font-size: 0.95em; + text-align: center; + text-decoration: none; + text-shadow: 0 1px 1px black; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + box-shadow: 0 0 5px black; + -moz-box-shadow: 0 0 5px black; + -webkit-box-shadow: 0 0 5px black; +} + +a.finish:hover, +a.finish:focus { + cursor: pointer; + background-color: rgb(255,255,255); + background-color: rgba(255,255,255,0.2); + box-shadow: 0 0 15px black; + -moz-box-shadow: 0 0 15px black; + -webkit-box-shadow: 0 0 15px black; +} + +a.finish:active { + background-color: rgb(255,255,255); + background-color: rgba(255,255,255,0.3); +} + +a.finish.disabled { + opacity: 0.2; +} + +a.finish.disabled:hover, +a.finish.disabled:focus, +a.finish.disabled:active { + cursor: default; + background-color: rgb(255,255,255); + background-color: rgba(255,255,255,0.1); +} + +.popup a.finish { + margin-top: 6px; + float: right; +} diff --git a/jappixmini/jappix/css/privacy.css b/jappixmini/jappix/css/privacy.css new file mode 100644 index 00000000..521be322 --- /dev/null +++ b/jappixmini/jappix/css/privacy.css @@ -0,0 +1,197 @@ +/* + +Jappix - An open social platform +This is the privacy CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 16/02/11 + +*/ + +#privacy .content { + padding: 10px 0 10px 0; +} + +#privacy .privacy-head div.list-left, +#privacy .privacy-head div.list-center, +#privacy .privacy-head div.list-right, +#privacy .privacy-first { + float: left; +} + +#privacy .privacy-head div.list-left { + margin-left: 5px; +} + +#privacy .privacy-head div.list-center { + border-right: 1px dotted #1b393f; + height: 24px; + width: 1px; + margin-right: 15px; + padding-left: 18px; +} + +#privacy .privacy-head span, +#privacy .privacy-head a, +#privacy .privacy-item span, +#privacy .privacy-item a { + float: left; +} + +#privacy .privacy-head span { + font-size: 0.9em; + font-weight: bold; + margin: 3px 10px 0 0; +} + +#privacy .privacy-head input, +#privacy .privacy-head select { + width: 180px; +} + +#privacy .privacy-head input { + margin-top: 1px; +} + +#privacy .privacy-head select, +#privacy .privacy-item select { + margin-top: -2px; +} + +#privacy .privacy-head span.left-space { + margin-left: 16px; +} + +#privacy .privacy-head a, +#privacy .privacy-item a { + width: 20px; + height: 20px; + padding: 0; + display: block; +} + +#privacy .privacy-item a.item-add { + background-position: 2px -1178px; +} + +#privacy .privacy-head a.list-remove, +#privacy .privacy-item a.item-remove { + background-position: 2px -1197px; +} + +#privacy .privacy-item a.item-save { + background-position: 3px -126px; + width: auto; + height: 19px; + padding: 1px 7px 0 21px; +} + +#privacy .privacy-item, +#privacy form, +#privacy .privacy-active { + clear: both; +} + +#privacy .privacy-item { + margin: 17px 12px; + font-size: 0.9em; +} + +#privacy .privacy-item span { + font-weight: bold; +} + +#privacy .privacy-item select { + width: 300px; + margin: -4px 30px 0 10px; +} + +#privacy .privacy-item a { + margin: -2px 6px 0 0; +} + +#privacy .privacy-first, +#privacy .privacy-second, +#privacy .privacy-third { + height: 195px; + font-size: 0.9em; + margin: 10px 0 0 10px; + float: left; +} + +#privacy .privacy-first, +#privacy .privacy-second { + border-right: 1px dotted #1b393f; + padding-right: 14px; +} + +#privacy .privacy-first { + width: 125px; +} + +#privacy .privacy-first label { + margin: 50px 0 0 15px; +} + +#privacy .privacy-first label input { + margin-top: 2px; +} + +#privacy .privacy-second { + width: 205px; +} + +#privacy .privacy-second label { + margin: 2px 0 0 12px; +} + +#privacy .privacy-second input[type=radio], +#privacy .privacy-third input[type=checkbox] { + margin-top: 2px; + margin-bottom: 2px; +} + +#privacy .privacy-second input[type=text], +#privacy .privacy-second select { + width: 170px; + margin: 2px 0 11px 12px; + float: none; +} + +#privacy .privacy-third label { + width: auto; + margin-top: 11px; +} + +#privacy .privacy-third { + width: 240px; +} + +#privacy .privacy-active { + margin: 34px 16px 0 16px; + font-size: 0.9em; +} + +#privacy .privacy-active-elements { + float: right; +} + +#privacy .privacy-active input[type=text] { + width: 30px; + margin: 0 0 0 8px; + float: none; +} + +#privacy .privacy-active input[type=checkbox] { + margin: 2px 8px 0 0; + float: left; +} + +#privacy .privacy-active label { + width: auto; + margin: 0 15px 0 0; + clear: none; +} diff --git a/jappixmini/jappix/css/rosterx.css b/jappixmini/jappix/css/rosterx.css new file mode 100644 index 00000000..4f159ac8 --- /dev/null +++ b/jappixmini/jappix/css/rosterx.css @@ -0,0 +1,53 @@ +/* + +Jappix - An open social platform +This is the Roster Item Exchange tool CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 05/02/11 + +*/ + +#rosterx .content { + padding: 10px 0 10px 0; +} + +#rosterx .rosterx-head a { + font-size: 0.9em; + margin: 3px 4px; + float: left; +} + +#rosterx .oneresult:hover { + cursor: pointer; +} + +#rosterx .oneresult span { + margin: 2px 5px 0 5px; + overflow: hidden; + float: left; +} + +#rosterx .oneresult span.name { + width: 230px; + font-weight: bold; +} + +#rosterx .oneresult span.xid { + width: 270px; + font-size: 0.9em; +} + +#rosterx .oneresult span.action { + width: 16px; + height: 16px; + margin-top: 4px; + float: right; +} + +#rosterx .oneresult span.action.modify { + background-position: 0 -1244px; +} diff --git a/jappixmini/jappix/css/search.css b/jappixmini/jappix/css/search.css new file mode 100644 index 00000000..505b17d9 --- /dev/null +++ b/jappixmini/jappix/css/search.css @@ -0,0 +1,60 @@ +/* + +Jappix - An open social platform +This is the search tool CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 23/01/11 + +*/ + +.search { + position: relative; +} + +.search input.suggested { + border-bottom: 1px solid white; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + -moz-border-radius-bottomright: 0; + -moz-border-radius-bottomleft: 0; + -webkit-border-bottom-right-radius: 0; + -webkit-border-bottom-left-radius: 0; +} + +.search ul { + background-color: rgb(255,255,255); + background-color: rgba(255,255,255,0.9); + border-color: #e1a014; + border-style: solid; + border-width: 0 1px 1px 1px; + position: absolute; + z-index: 1; + padding: 3px 0; + list-style: none; + overflow: auto; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; + -moz-border-radius-bottomright: 3px; + -moz-border-radius-bottomleft: 3px; + -webkit-border-bottom-right-radius: 3px; + -webkit-border-bottom-left-radius: 3px; +} + +.search ul li { + padding: 2px 6px; + color: #3d3d3d; + text-shadow: none; +} + +.search ul li:hover { + cursor: pointer; +} + +.search ul li.hovered { + background-color: rgb(225,160,20); + background-color: rgba(225,160,20,0.3); +} diff --git a/jappixmini/jappix/css/smileys.css b/jappixmini/jappix/css/smileys.css new file mode 100644 index 00000000..faad478b --- /dev/null +++ b/jappixmini/jappix/css/smileys.css @@ -0,0 +1,196 @@ +/* + +Jappix - An open social platform +This is the smileys CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 16/02/11 + +*/ + +.emoticon { + width: 16px; + height: 16px; +} + +a.emoticon { + margin: 2px; + float: left; +} + +a.emoticon:hover, +a.emoticon:focus { + opacity: 0.8; +} + +a.emoticon:active { + opacity: 0.7; +} + +img.emoticon { + border: 0 none; + vertical-align: bottom; +} + +.emoticon-biggrin { + background-position: 0 0; +} + +.emoticon-devil { + background-position: -16px 0; +} + +.emoticon-coolglasses { + background-position: -32px 0; +} + +.emoticon-tongue { + background-position: -48px 0; +} + +.emoticon-smile { + background-position: -64px 0; +} + +.emoticon-wink { + background-position: -80px 0; +} + +.emoticon-blush { + background-position: -96px 0; +} + +.emoticon-stare { + background-position: -112px 0; +} + +.emoticon-frowning { + background-position: -128px 0; +} + +.emoticon-oh { + background-position: -144px 0; +} + +.emoticon-unhappy { + background-position: -160px 0; +} + +.emoticon-cry { + background-position: -176px 0; +} + +.emoticon-angry { + background-position: -192px 0; +} + +.emoticon-puke { + background-position: -208px 0; +} + +.emoticon-hugright { + background-position: -224px 0; +} + +.emoticon-hugleft { + background-position: -240px 0; +} + +.emoticon-lion { + background-position: -256px 0; +} + +.emoticon-pussy { + background-position: -272px 0; +} + +.emoticon-bat { + background-position: -288px 0; +} + +.emoticon-kiss { + background-position: -304px 0; +} + +.emoticon-heart { + background-position: -320px 0; +} + +.emoticon-brheart { + background-position: -336px 0; +} + +.emoticon-flower { + background-position: -352px 0; +} + +.emoticon-brflower { + background-position: -368px 0; +} + +.emoticon-thumbup { + background-position: -384px 0; +} + +.emoticon-thumbdown { + background-position: -400px 0; +} + +.emoticon-lamp { + background-position: -416px 0; +} + +.emoticon-coffee { + background-position: -432px 0; +} + +.emoticon-drink { + background-position: -448px 0; +} + +.emoticon-beer { + background-position: -464px 0; +} + +.emoticon-boy { + background-position: -480px 0; +} + +.emoticon-girl { + background-position: -496px 0; +} + +.emoticon-phone { + background-position: -512px 0; +} + +.emoticon-photo { + background-position: -528px 0; +} + +.emoticon-music { + background-position: -544px 0; +} + +.emoticon-cuffs { + background-position: -560px 0; +} + +.emoticon-mail { + background-position: -576px 0; +} + +.emoticon-rainbow { + background-position: -592px 0; +} + +.emoticon-star { + background-position: -608px 0; +} + +.emoticon-moon { + background-position: -624px 0; +} diff --git a/jappixmini/jappix/css/stats-svg.css b/jappixmini/jappix/css/stats-svg.css new file mode 100644 index 00000000..f512a939 --- /dev/null +++ b/jappixmini/jappix/css/stats-svg.css @@ -0,0 +1,71 @@ +/* + +Jappix - An open social platform +This is the SVG stats CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Erwan Briand, Vanaryon +Last revision: 20/11/10 + +*/ + +svg { + background-color: #e8f1f3; +} + +.refline { + stroke: #596171; + stroke-width: 2px; +} + +.refleft { + fill: #000000; + font-family: "Inconsolata", "DejaVu Serif sans", Verdana, sans-serif; + font-size: 8px; +} + +.reftext { + fill: #586070; + font-family: "Inconsolata", "DejaVu Serif sans", Verdana, sans-serif; + font-size: 10px; +} + +.bubbletextblue, +.bubbletextred { + fill: none; + font-family: "Inconsolata", "DejaVu Serif sans", Verdana, sans-serif; + font-size: 8px; + text-anchor: end; +} + +.bluebar { + fill: "#6C84C0"; + fill-opacity: "0.6"; +} + +.gbar:hover .bluebar { + fill: #2A3F73; +} + +.gbar:hover .redbar { + fill: #C70705; +} + +.gbar:hover #bubble { + fill: white; + stroke: grey; +} + +.gbar:hover .bubbletextblue { + fill: #2A3F73; +} + +.gbar:hover .bubbletextred { + fill: #C70705; +} + +.gbar:hover .reftext { + fill: #000000; +} diff --git a/jappixmini/jappix/css/tools.css b/jappixmini/jappix/css/tools.css new file mode 100644 index 00000000..8b689f54 --- /dev/null +++ b/jappixmini/jappix/css/tools.css @@ -0,0 +1,346 @@ +/* + +Jappix - An open social platform +This is the tools CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 27/08/11 + +*/ + +#top-content .tools { + background-color: rgb(232,241,243); + background-color: rgba(232,241,243,0.6); + padding: 3px 8px 5px 8px; + min-width: 10px; + height: 17px; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-bottomright: 4px; + -webkit-border-bottom-left-radius: 4px; + -webkit-border-bottom-right-radius: 4px; +} + +#top-content .tools a { + color: black; + padding: 0 3px; + margin: 0 1.5px; +} + +#top-content .tools a:hover, +#top-content .tools a:focus { + cursor: pointer; + text-decoration: underline; +} + +#top-content .tools-logo { + background-position: 9px 2px; + width: 74px; + float: left; +} + +#top-content .tools-all { + float: right; + text-align: right; + margin-left: 8px; + font-size: 0.9em; + color: black; +} + +#top-content .notifications { + background-position: 7px -1264px; +} + +#top-content .music { + background-position: 6px -1286px; +} + +#top-content .notifications:hover, +#top-content .music:hover { + cursor: pointer; +} + +#top-content .music:hover, +#top-content .notifications:hover, +#top-content .music:focus, +#top-content .notifications:focus { + background-color: rgb(232,241,243); + background-color: rgba(232,241,243,0.7); +} + +#top-content .music:active, +#top-content .notifications:active { + background-color: rgb(232,241,243); + background-color: rgba(232,241,243,0.8); +} + +#top-content .actived, +#top-content .actived:hover, +#top-content .actived:focus, +#top-content .actived:active { + background-color: rgb(232,241,243) !important; + background-color: rgba(232,241,243,0.9) !important; +} + +#top-content .notify { + background-color: #c60505; + color: white; + font-size: 0.7em; + font-weight: bold; + margin-left: -10px; + padding: 1px 4px; + position: absolute; + bottom: -2px; + border-radius: 10px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; +} + +#top-content .tools-content { + display: none; + position: absolute; + top: 25px; + margin-left: -8px; +} + +.tools-content-subarrow { + background-position: 0 -241px; + opacity: 0.8; + height: 10px; + width: 18px; + margin-left: 12px; +} + +.tools-content-subitem { + background-color: rgb(0,0,0); + background-color: rgba(0,0,0,0.8); + padding: 14px 6px 6px 6px; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; +} + +.notifications-content { + width: 240px; +} + +.notifications-content .tools-content-subitem { + max-height: 250px; + color: white; + text-shadow: 0 1px 1px black; + text-align: left; + overflow-x: none; + overflow-y: auto; +} + +.notifications-content .empty { + color: white; + font-size: 0.9em; + text-decoration: underline; + margin: -8px 4px 2px 0; + display: none; + float: right; +} + +.notifications-content .nothing { + font-size: 0.9em; + margin: 5px; +} + +.notifications-content .one-notification { + padding: 6px 4px; + font-size: 0.85em; + clear: both; + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; +} + +.notifications-content .one-notification:hover, +.notifications-content .one-notification:focus { + background-color: rgb(255,255,255); + background-color: rgba(255,255,255,0.1); +} + +.notifications-content .one-notification:active { + background-color: rgb(255,255,255); + background-color: rgba(255,255,255,0.2); +} + +.notifications-content .avatar-container { + float: left; + width: 40px; + height: 40px; + margin: 0 8px 8px 0; + text-align: center; + background-repeat: no-repeat; +} + +.notifications-content .avatar { + max-width: 40px; + max-height: 40px; +} + +.notifications-content .notification-text, +.notifications-content .notification-actions { + margin-left: 48px; + overflow: hidden; +} + +.notifications-content .notification-actions { + margin-top: 3px; +} + +.notifications-content .notification-actions a { + color: white; + font-weight: bold; + font-size: 0.9em; + text-decoration: underline; + margin-right: 8px; +} + +.notifications-content .one-notification .notification-actions span.talk-images { + background-position: 0 -1828px; + width: 16px; + height: 16px; + margin: -1px 6px 0 0; + float: left; +} + +.notifications-content .one-notification[data-type=subscribe] .notification-actions span.talk-images { + background-position: 0 -1796px; +} + +.notifications-content .one-notification[data-type=invite_room] .notification-actions span.talk-images { + background-position: 0 -1812px; +} + +.notifications-content .one-notification[data-type=send] .notification-actions span.talk-images, +.notifications-content .one-notification[data-type=send_accept] .notification-actions span.talk-images, +.notifications-content .one-notification[data-type=send_reject] .notification-actions span.talk-images, +.notifications-content .one-notification[data-type=send_fail] .notification-actions span.talk-images { + background-position: 0 -1956px; +} + +.notifications-content .one-notification[data-type=rosterx] .notification-actions span.talk-images { + background-position: 0 -1844px; +} + +.notifications-content .one-notification[data-type=comment] .notification-actions span.talk-images { + background-position: 0 -1860px; +} + +.notifications-content .one-notification[data-type=like] .notification-actions span.talk-images { + background-position: 0 -1876px; +} + +.notifications-content .one-notification[data-type=quote] .notification-actions span.talk-images { + background-position: 0 -1892px; +} + +.notifications-content .one-notification[data-type=wall] .notification-actions span.talk-images { + background-position: 0 -1908px; +} + +.notifications-content .one-notification[data-type=photo] .notification-actions span.talk-images { + background-position: 0 -1924px; +} + +.notifications-content .one-notification[data-type=video] .notification-actions span.talk-images { + background-position: 0 -1940px; +} + +.music-content { + width: 220px; +} + +.music-content .tools-content-subitem { + height: 247px; +} + +.music-content .player { + background: #b5d5db; + background: -moz-linear-gradient(top, #b5d5db, #adced4); + background: -webkit-gradient(linear, left top, left bottom, from(#b5d5db), to(#adced4)); + height: 20px; + padding: 2px 5px; + border-top-right-radius: 4px; + border-top-left-radius: 4px; + -moz-border-radius-topright: 4px; + -moz-border-radius-topleft: 4px; + -webkit-border-top-right-radius: 4px; + -webkit-border-top-left-radius: 4px; +} + +.music-content .player a { + margin: 2px; + height: 16px; + width: 16px; + float: left; +} + +.music-content .player a:hover, +.music-content .player a:focus { + opacity: 0.8; +} + +.music-content .player a:active { + opacity: 0.6; +} + +.music-content .stop { + display: none; + background-position: 0 -270px; +} + +.music-content .list { + background-color: #e8f1f3; + height: 188px; + padding: 5px; + text-align: left; + overflow-y: auto; + overflow-x: hidden; +} + +.music-content p.no-results { + display: none; + color: black; + font-size: 0.9em; +} + +.music-content div.special { + padding-bottom: 2px; + margin-bottom: 6px; + border-bottom: 1px solid #c3d4d7; +} + +.music-content .song { + display: block; + margin: 3px 0; + font-size: 0.8em; +} + +.music-content .playing { + font-weight: bold; +} + +.music-content .search { + background-color: #e8f1f3; + height: 25px; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-bottomright: 4px; + -webkit-border-bottom-right-radius: 4px; + -webkit-border-bottom-right-radius: 4px; +} + +.music-content .search input { + margin: 2px; + width: 198px; + height: 15px; +} diff --git a/jappixmini/jappix/css/userinfos.css b/jappixmini/jappix/css/userinfos.css new file mode 100644 index 00000000..59bab655 --- /dev/null +++ b/jappixmini/jappix/css/userinfos.css @@ -0,0 +1,100 @@ +/* + +Jappix - An open social platform +This is the user-infos CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 27/03/11 + +*/ + +#userinfos .content { + overflow: auto; +} + +#userinfos .one-lap a { + text-decoration: underline; +} + +#userinfos .main-infos { + margin: 20px 20px 8px 20px; + height: 120px; + background: white; + position: relative; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; +} + +#userinfos .avatar-container { + float: left; + text-align: center; + margin: 20px 35px; + width: 80px; + height: 80px; +} + +#userinfos .avatar { + max-width: 80px; + max-height: 80px; +} + +#userinfos h1, +#userinfos h2, +#userinfos h3 { + width: 410px; + overflow: hidden; +} + +#userinfos h1 { + font-size: 2em; + padding-top: 12px; + margin-bottom: 4px; +} + +#userinfos h2 { + color: #447079; + font-size: 1.1em; + margin-bottom: 10px; +} + +#userinfos h3 { + color: #6e8388; + font-size: 0.8em; +} + +#userinfos .main-infos div.shortcuts { + position: absolute; + top: 10px; + right: 12px; +} + +#userinfos .block-infos { + margin: 7px 20px; + float: left; +} + +#userinfos .one-line { + margin: 4px 0; + font-size: 0.9em; + float: left; +} + +#userinfos .one-line b { + width: 120px; + float: left; +} + +#userinfos .one-line span.reset-info { + float: left; + width: 460px; +} + +#userinfos textarea { + margin: 30px 0 0 30px; + width: 572px; + height: 292px; +} diff --git a/jappixmini/jappix/css/vcard.css b/jappixmini/jappix/css/vcard.css new file mode 100644 index 00000000..59dfc486 --- /dev/null +++ b/jappixmini/jappix/css/vcard.css @@ -0,0 +1,106 @@ +/* + +Jappix - An open social platform +This is the vCard CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 16/01/12 + +*/ + +#vcard label { + font-size: 0.94em; + margin-top: 2px; +} + +#vcard #vcard-avatar input[type=file] { + margin-left: 15px; +} + +#vcard .avatar-container { + float: left; + text-align: center; + margin: 20px 0 35px 35px; + max-width: 96px; + max-height: 96px; +} + +#vcard .avatar { + max-width: 96px; + max-height: 96px; +} + +#vcard .avatar-delete { + background-position: 3px -1195px; + margin: 12px 25px 0 025px; + padding-left: 20px; + font-size: 0.9em; + float: right; +} + +#vcard .no-avatar { + width: 300px; + color: #7c2222; + padding: 10px; + margin: 15px 0 20px 40px; + background: #f8cece; + border: 1px #ba6d6d solid; + font-size: 0.8em; +} + +#vcard .forms textarea { + height: 111px; + width: 358px; + margin: 5px 12px 10px 12px; +} + +#vcard .forms .avatar-info { + border-width: 1px; + border-style: solid; + display: none; + width: 370px; + height: 15px; + font-size: 0.85em; + padding: 10px; +} + +#vcard .forms .avatar-wait { + background-color: #9bcbed; + color: #0a3858; + border-color: #306780; +} + +#vcard .forms .avatar-ok { + background-color: #c4ed9b; + color: #325213; + border-color: #578030; +} + +#vcard .forms .avatar-error { + background-color: #e79595; + color: #6a0b0b; + border-color: #7c1010; +} + +#vcard .infos { + width: 179px; + height: 328px; + margin: 15px 15px 15px 0; + padding: 0 8px; + float: right; +} + +#vcard .infos a { + text-decoration: underline; +} + +#vcard .send { + float: right; +} + +#vcard .send:hover { + cursor: pointer; +} diff --git a/jappixmini/jappix/css/welcome.css b/jappixmini/jappix/css/welcome.css new file mode 100644 index 00000000..71b31ef2 --- /dev/null +++ b/jappixmini/jappix/css/welcome.css @@ -0,0 +1,170 @@ +/* + +Jappix - An open social platform +This is the welcome tool CSS stylesheet for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 26/04/11 + +*/ + +#welcome .infos { + margin: 15px; +} + +#welcome .infos p { + margin-top: 6px; +} + +#welcome .infos p.infos-title { + margin-top: 0; +} + +#welcome a.box { + background-color: #e4eef9; + border: 1px solid #ccdbde; + margin: 12px 11px 4px 15px; + padding: 10px; + width: 270px; + text-decoration: none; + float: left; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; +} + +#welcome a.box.share { + width: 350px; + margin: 4px 130px; + padding: 4px 10px; + clear: both; +} + +#welcome a.box.share.first { + margin-top: 0; +} + +#welcome a.box.share:hover span.go { + display: block; +} + +#welcome a.box:hover, +#welcome a.box:focus { + border: 1px solid #93c5fa; +} + +#welcome a.box:active { + border: 1px solid #419afa; +} + +#welcome a.box.enabled { + background-color: #f1f6fd; + border: 1px solid #9dc4fc; +} + +#welcome a.box span { + margin: 3px 0; + display: block; +} + +#welcome a.box span.logo { + height: 35px; + width: 35px; + margin-right: 15px; + float: left; +} + +#welcome a.box span.logo.facebook { + background-position: 0 0; +} + +#welcome a.box span.logo.twitter { + background-position: -35px 0; +} + +#welcome a.box span.logo.buzz { + background-position: -70px 0; +} + +#welcome a.box span.logo.identica { + background-position: -105px 0; +} + +#welcome a.box span.option, +#welcome a.box span.name { + font-size: 0.9em; + font-weight: bold; +} + +#welcome a.box span.description { + font-size: 0.7em; + margin-top: 7px; +} + +#welcome a.box.share span.description { + margin-top: 4px; +} + +#welcome a.box span.image { + height: 16px; + width: 16px; + margin: -30px 12px 0 0; + float: right; +} + +#welcome a.box span.image.sound { + background-position: 0 -900px; +} + +#welcome a.box span.image.geolocation { + background-position: 0 -658px; +} + +#welcome a.box span.image.xmpp { + background-position: 0 -990px; +} + +#welcome a.box span.image.archives, +#page-engine .text .tools-archives { + background-position: 0 -1025px; +} + +#welcome a.box span.image.offline { + background-position: 0 -80px; +} + +#welcome a.box span.tick, +#welcome a.box span.go { + height: 16px; + width: 16px; + display: none; + float: right; +} + +#welcome a.box span.tick { + background-position: 0 -1661px; + margin: -52px -15px 0 0; +} + +#welcome a.box span.go { + background-position: 0 -1120px; + margin: -28px 5px 0 0; +} + +#welcome a.box.enabled span.tick { + display: block; +} + +#welcome div.results { + margin: -7px 15px; + padding: 0; + height: 272px; + overflow: auto; +} + +#welcome .bottom .finish.save { + display: none; +} diff --git a/jappixmini/jappix/favicon.ico b/jappixmini/jappix/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..dc20377607e8f35f1e28c0cf77fa4030cc83e430 GIT binary patch literal 1150 zcmbW0ODKd<6vxj*2r(&2B&FoBw6n2LY@{fbvYfDEFRQT?YuT`{WMw-OV<*bSW0I0m zN=kW;-=FW+Y5Epl{?l*Hn#EWnn<)5Rt8CwBy>LR%d@8IWpA?}ByI-}6{9};Wbq&lL|_iGQW37WOW z5@@f!U;YN@ZsVl$AAxIl1=Syb?k?;_cc$@87zVFef>ZEojztAiSbv^+17Z*K%q6{@ zI|%(>_%$F6fYuFTpige4>ruz zURE0yc91-enM3bFXO~3!zpg?N9$*VJua&aEDcnP$(JVJ)cC&2u6d=x#&vHsxZu$gD IVg>SMKX#aZ`Tzg` literal 0 HcmV?d00001 diff --git a/jappixmini/jappix/img/others/blank.gif b/jappixmini/jappix/img/others/blank.gif new file mode 100644 index 0000000000000000000000000000000000000000..35d42e808f0a8017b8d52a06be2f8fec0b466a66 GIT binary patch literal 43 scmZ?wbhEHbWMp7uXkcLY|NlP&1B2pE7Dgb&paUX6G7L;iE{qJ;0LZEa`2YX_ literal 0 HcmV?d00001 diff --git a/jappixmini/jappix/img/others/default-avatar.png b/jappixmini/jappix/img/others/default-avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..7cda4c64b8c5eea35bdd7f1f5d54515578e3cb62 GIT binary patch literal 1436 zcmV;N1!MY&P)38p0aN4mY5#{O{9=IUH$Pl-36pp#n|Jr z>Ru6vhv!Sv+$pOrQ)m}?ksm;-U0teZI|1n4`ncWRBoYrsxw~+Hbe$LUVI?*F1ZeAE z?1;+pJx69|dqv{@$eMj$>9uX)W zq+7gD|A!>f-L6{F*bxADOvDaM&b^?}HhG`Ew-ClH^N9wK&hv9&()R4+eoA z&ntAl@&rNKch;OOP4KgaT9w-EkId=PltR12+fBvTZvp8lPu5pSbia&N8!w6&fGX{h zNGs(U?OS)gVaT?R>gE#>#BS&{h3=w4I?59ZNfK%LbW>;-c+jy)VqOZt>5lSXV-y%`h3|kSiq`;iF@%4>53w3R8Cvw)`A(gC zRw4bygN;bx!dEz@7rOxf-4)>@ zTvedZE^hz>ED0YeYZ~GKZUEi#uDA=T`5OvtXJZ(^f$#}cwQnoqmUwU*L0nQJd_fv} zNNKtq|H9ivW$1fpXojn`g%1F_0q8EiMEA9<=Et~1JkAKH%e<|2P@0ASsjkrd80U$H zI-FSY^rb7`BaOSxNozn_kZA8j_}07PeL4Gy6YlXfe?S*w3c7XGQ0P7nr1)xf7ob~= z$N(2W69iInp7%+ltC5}cS75TSP52I&b1x|!X9A|8?IGbiN)unKxD9BpM0WzxM1fqq zXl)%g3(tUY-^G^wQZ?EuLZPf_Bqd)8LYaIx#*l4`AQOK_faSn5`feaF#^W-26a zvtNMrrZOZI(s)-F0IBm|{{Z$|-aA%RYYc(!IbTD|B}4tg7hA3exM5bTiBkgm$wXv>T43 zjj$U4Lw5q(dgetetiLG+r7Go_@xX z0g9HPQ=Dr~1}IV{m&pLd=5p)F00j!Py_5`4@a=U@rY8dwsIeKlGZ~=RmNB0u0~Fgr q_+TrX+877l!}s{b%+Ad7K3vk;M!Q z+`=Ht$S`Y;1W-`2#5JPCIX^cyHLrxhxhOTUBsE2$JhLQ2!QIn0AiR-J9H@xf)5S4_ fLpC{K1}6i<9EMrzTxQ(>3Nv`R`njxgN@xNApadXJ literal 0 HcmV?d00001 diff --git a/jappixmini/jappix/img/sprites/animate.gif b/jappixmini/jappix/img/sprites/animate.gif new file mode 100644 index 0000000000000000000000000000000000000000..cbf55da59291fb1d338d446756a4fca56916cd34 GIT binary patch literal 1898 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 IM6N<$f}^<@cK`qY literal 0 HcmV?d00001 diff --git a/jappixmini/jappix/img/sprites/background.png b/jappixmini/jappix/img/sprites/background.png new file mode 100644 index 0000000000000000000000000000000000000000..4ae286d83f610c67efd5fa2d55ba92478bfcc363 GIT binary patch literal 8782 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 literal 0 HcmV?d00001 diff --git a/jappixmini/jappix/img/sprites/browsers.png b/jappixmini/jappix/img/sprites/browsers.png new file mode 100644 index 0000000000000000000000000000000000000000..8fadb7b3836c59c35eccbe8aa7cb7814366017be GIT binary patch literal 10955 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 literal 0 HcmV?d00001 diff --git a/jappixmini/jappix/img/sprites/buttons.png b/jappixmini/jappix/img/sprites/buttons.png new file mode 100644 index 0000000000000000000000000000000000000000..4c32b14dcc2a7862a7e2f4a2bdb2793ac5300698 GIT binary patch literal 379 zcmeAS@N?(olHy`uVBq!ia0vp^j6kHs!3JbmMD17#q}Y|gW!U_%O?XxI14-? ziy0WWg+Z8+Vb&Z8prB-lYeb22er|4RUI~M9QEFmIYKlU6W=V#EyQgnJcq5-UP|-I} z7sn8b-m{Z8hO!y*G}~t;OEfAb@VX`PSXs&O3cBj~75x{TGX2o`z8p*aX_dcgdYNZ% zPk!Q>ct-qcs?mJ9e)IP4BI(6wM%?kiT1(ayfy?ZJ}JxUDwJdnSCoyvkk5-1fY@ z|4x~sQzy2xZeU?c{=U0WNq)tmKttYJE5z&)wMs6QYx(IU_=fDOxbMf$zSn*6?0NU+ ze>dds-LI5jve!3pk*wE}_h%-&&|JURYhsw>+O|7xy0b4_pW=C6tF>KjrOn38S5|Gb z{j==(@7GWN|Gvm7lPGXbsq#9@@(ZzBlM8okm)6y&Ua@yuSsJ6+G@Y8|Zuc5G>~A}+ V4b^@BNdy=I44$rjF6*2UngE;PlMVm? literal 0 HcmV?d00001 diff --git a/jappixmini/jappix/img/sprites/home.png b/jappixmini/jappix/img/sprites/home.png new file mode 100644 index 0000000000000000000000000000000000000000..cd3ca4a19cd96c4e89f91ffa2183d180275c3e91 GIT binary patch literal 28465 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 T=SkqzK5L81=ii+3O!8-hKDmv*&F8 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 H(~$oKZFmFf literal 0 HcmV?d00001 diff --git a/jappixmini/jappix/img/sprites/logs.png b/jappixmini/jappix/img/sprites/logs.png new file mode 100644 index 0000000000000000000000000000000000000000..db99a20643aae76607e3df10bc1ea82196889e81 GIT binary patch literal 2355 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 literal 0 HcmV?d00001 diff --git a/jappixmini/jappix/img/sprites/me.png b/jappixmini/jappix/img/sprites/me.png new file mode 100644 index 0000000000000000000000000000000000000000..8a8bc0fb3b8be42ac326d9b209b5815a641e546a GIT binary patch literal 5100 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 O0000C}^ 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 literal 0 HcmV?d00001 diff --git a/jappixmini/jappix/img/sprites/talk.png b/jappixmini/jappix/img/sprites/talk.png new file mode 100644 index 0000000000000000000000000000000000000000..a307e19ebceb827a0be5f7c7a848500ea8c3d4a3 GIT binary patch literal 43207 zcmZs?V{|S-(=Hs_wr$(CZR|MNvAJX0PVU&Yy<=;~w#_fk^SFMg3 zTGd@wU3Y|%f+Rc)4h#?w5WKXMn99#t3J3_Y2@2w;c88-@2?SIYD=j9h=COW}4X-uj z{yo~uhGA!G%gTJ}=8kKg>&~ajEnbMmk5oV@LO|K%#8Wh+qCulf!#tr{_76_=cZ;C8 z+rNl-f>k%~u|fYz=E|AR-1B$mJl8xvy^Zhh-^ceIH7iTk?g}~6k6*q$*W29srFkAA znf+K=5S7NzA4nX?(Uy!s#GroQxX^=TJ3Om8AIuKHe`ntEd!S^A`Na&4(@pCj7*PG; zY6d!I&!s_65Vp}mZ~r_%X~GUh2j>kS?8ONKZNP6M#YI%hrR?yxK`opA1Aq?9@Ry4`H}I;6(DEB-~S?f2x-m$-pq&225S73wl=Wt(Rb?P4JH?K z>Lpk^gZ1i@DT4~f_ndI&h6`Llw8Vl!liqJgfIO?G%*je54B5Vomnt;h2r(iYGIA$$ zy0Ze|XUJd+g-){r-!{?r><4Q22S%5uzjepEPU7hozo)%Xyp;HQ(aG>@;AL!#B?s$Sdm6yWV+%T?9eC=i?C%dZo$piK+qCcFj6d11Vn-8h~6>3KziBn5C)R?%!KSx%uM=tn?X7# z;1)1ZvTHGwVzQ^#yAYs%drApC8grAq6nKEy5M$4NV}t!7NrU|B=MbuK17608G<(UD zT2D)M0>GTz5-#MMQolH!a{XiFj-aKs^Rj{V&ru1i{ zmwJYP{jR=t%x*ykRgK)fw?Jwvi7AWx4sEe zkxp((KYI{3gu?yqUC*eoarFv1O`2*e7^UoRS23c$Z`aKzcE9gw-fq*mcU=>!2JB?EKn7qw8rTO4xMx| zw;K>-5jCXQELw&i7Hv?9k)!+h7unyO1_9TDCQ|eJb(Ka~p~nAv9DS#I8pyX4cEjajCIRBCZ~{^Y5DRB!r~oar4tO#{?wZ02#MYqDn}8 zK#wxOelif@^_NKmypWQ`F&;EkX3DiULXxZBc(XEEnsvb(Ucb0I^OK-te=`nH z-)yj*VZ9wE->(;xhK>HisL+I?5@+i0i}Fr+*56<)T?wq2@fXr!v37}ACv`s9LWVw5 zazTlCUoD>HcRt`wdqfx}MOi8qHLaGl1H7lf>lM<7a#?uMpQtRewoSw`H1Z}gVK7OT zr;}mQ(ceuvy?MEu-7JtMUATzu+rroZAhYq?nx5AL3ix4=qiZIr5Q?v-FXA_uXU9E2 z2FQv{5>@7;gL~T9`25DqI@5&@dmrV$Z=T21qB>IC{Cn^1`;%SLtzD6SH{3Oo8QU5q zLM1i*Gj5(@cpL}j4kCHG5Zbnt-2UaBvv9iaZr(KG^ z5FQfb5EIFGJ{>gp=g*VCI1gU8jV(Wumg zF$FcVjbLFA$EGZZ)||`}^yeCHLmqD%TUcowKa zThU_?D~&J$qVbhR3<35%qFlnu%geP;|HsFrTS)#rMjg@P+j%s*wb;m9Q zQ?SQrIls<$f)B0_k}j`(|0Q4}UKCuG$M7%W^`bHs zsg^)mWew)SCNAX3^inmG?u?zw^{D;nT^jDG(U@UF2y@_}^QV1+ekFpWOZh;Nnz2d+ z0{xZJqvy##b*SRAK9R}_(EiJcrD7KJH$9snUYT|lK{+Nk#pV&dDFxivQ;Hc}%YQ4LP^qb@M@pu~#sxN;9nCf3 zQ5cz;hQ3RBOo$-!J1l>KVHM^Z%qF1Ht_w$EaBaXqg|ay8T%f{0KtQ0ysE~nyX2Sumgj%14mA+UOdb%Up_(b3 zk$F-ch*(BbIsb2B8;37RT+KB2xe8{5VDk`NON;u>tFCIUHdb z$<-3PoTnqp58!nag8AflUL?bd15Da4P#>zX3}aLkG2B;sL{L9SUz%_6)<>0SPV#nK zA>+H`95*evWG{xwX8!LkBfa#GMk_}cj?QCAtblC|U@VQTh<&#e2j+Q@yCbpe0%&fS zuTa$Z!5PZmV;30hpNOQ{dqgawDAzS=PI&3L^UFRXV*a7LtBV`0vLDmj7f~UpvKmVW zs232uU48*5ae8)Kc&r4<;dBw>hDQYd&hK!hUT#Ip9z>K5XFm~lkLX$3*4-IM=Kpo& z_i9R@#t7`s21p$;s0Ec}jBIHyhR9#w#Mx&K@SFd>sXr65L|a8fPov95?KRM^Oq{MR zw`JW^LzEA@h$^Dv0H%T8h8MuCXRw<&)w*RY9vGi&`~iN^Iuh^Vq6kvEye`u*hywZJ zaVE$LVEP7i@$(_rjLab<%G)1BWwqRNI45^Z+~-IN!ItCXpNdaY>{J!^2>bT`PUQq| zsB@a|vk<8+wOFOAg0G zQU(@p2p|HPaMjlO-MR7XwaWYUz%OZ3)U_kbxKP5D_tl@U!0rk4;)c;pMLp!>(#9zZD(hPSMPYxlR+kF4e+w2 z!;l56*=|{~WV97Q|7YBv!Q&-EPIbmWJ!Oubv~tl7@?Jvalpsu%fA!FWO9W5G!(C0p zKXK;Vqi2|7^Kq?j_t3eFr~RTayNO2|_fbBHW*o~-?eEP960s=yyCWzUI13HU^1^uU zed|Asf$cNmGZsjof+TsNDiJN4DVs@n-=%a=4PmcQ&Pz2}ruRQO%+V@arIWGBo7YUv z*H7ZVZ<6t2q7t*ZZeqLpC}=BMi&w8HZLivLa_En(Rs06MRD4LTl9(uLQnXC_qt>SJ75Yqo$B;% z$!URi^nKs8K56kV%k)Gc#q_cK@B!|NxG(sh;F-_>*5=X1yxfK!PcD2eswZ zV+BQ!Ds&m9Q2KC^vx3-(Y)^Yx$zN> ziN$hgrdN-JOGo0C4+u3|mLhw(KAUu>(*EN(k&F!0Y-^i-QUqFZQFdaqbyH_zcRenu z7X*z@%(Ypt!^w>!LmRZkGQIo1k*5=GhO8QBi7yY*s}acX$5ggt+spK+&aN~}z0J)` zyxeNa3v1x@KYzPYTJe^aFT@6_khYZKadc<-81HF_rg2s}Lh|l&UNTC3QeCDKn>`b} z*Bq$5FBgG^J|{Y<7|PmZv}qS#laQ#9ae&o^vwV{?pV1-#wtSRJ z4kF-l?@2_i4<}*JVPxA};Uyp&k=@XVFS??YuAyIWSNdEvwD7o1QUxZcQD8-u8kwyB zK^9+A(KMJCQ)`F(Ior{?^B3+*1bJ@O#^R&KfZ`{XJgYY066XfLLAhm6UfQ?RFi#=V zJHV}l8?x6Na7qT%k>L$Tm0bu?*hLsU7$bMeoaAGd^OXJlNY9+z^98PZ4t|H-PV#Wc zl#B;H6`1xqKzB1a3AYVQgLry+dC5OZyyrCYpgvU!hR!r#UV4X+JOp<2 zltUxtCV*FmRDPoU4ijz5n^OWBbp3Y-?FCGF6l5vAJQx1=JZq|_1Rm<)DzucE0N9-} zfHz&J=LwP1r0s>fJ%67`tq1t=pKDVQe(CW3;F<`uOX&o7Hvz%ElSgMG(p?kD!SB%d z7RA_Qw)T8~cz0Et=zqaZ=b*Q3v#57__5}bgn4T84_TbijXB*_~P;93nKNA1x)Z%W? zQfBN_)w!}QIt&gU6U*?Pdsr3FH^=*#2!-D4BD=hqNl;OAX`pc4;wK{smtx zF&6@)h*aObP;U8rZt1f3BZ(=D1g*!O9nFCsk0X&FaXZHmX?!dVW*c#%y%pH%ir#h9 zx^pdEqTIDY^+tc&kiEh@-vuZ0DS|WX9bHJbE5AfzE#X*XTyKuL$!S=qT=f4VWWh){ z;{Op}oMBo2ytrsr<+5>|@QO*SOV|EsDx6_vKpDCTwL#4no_Dw0r!+w7|4*QZ*&_cV zJ`xEtRCn0oIK$uN$`9Ha6LXO%fJYiMQ#iiXqkesKXp1#Ct{TMxWxn2hY__9ruwvr` zyyDk}Y${J{12Ma|=Uc~t6-zWodxrisME~#-XV&Yh9Nb3ZNgo`-+HanY8UL`K33-yg zO&smY%r9_#ekE~zuGkkEouf8nml`pC0LK)vMV?7^w7BaXG7c#7sK+k-T8*Mupql@$ zpol=lko-Tgcf3r6u=0g51zxG-M?zpmsR5vVR)5gX5(Sjui08e!vK@^UOlU#XdJDxo zbqH8M<7xVf0Z!Pf#jM`4pkC3V3%Yy!T$)`(wgJj&F(#r07rog;rfzUCdu_^iUAK`f z?&Gne1$3t9I8GRS8aI6ZXK3Q!+5gq(fAsQWh%vGF>jbC95C(9g=4)oYJD41-BSu-C zU^NzC&5@|5F%^)RFLX4sK)Y)d-_!e0RDN(Hi`+^Naju_PpsM(JDYxj{I=v$AYf@^X z;(+rQ!+d%-gqofYyqH33H1z!mr|U^^#eUo5>hzfDRjDSr+&_iGNmgjr{u1@p6KH5t zd4aNXNpqP3VLx{EUqKSFM6>#TjVXpy^vob$ zlb`I(NcG~|+pQUDu{SZ9xii4oEr!y^rmItSBiuyw$eKbVjbA}OprrHO z&#jvImer9oGGvR@k3zz8RxTNG=3}iHMA7c2QG4YGFl50)u$c>L_F;SAF)b80jCSB{ z2@qxuBV4!~iNaN`l3$MmTd=xWzz)g>-FNusL=$lbjUqovEc8rDH60_?evRfCLsG5m zLtMCu#4hr6rv0NQvN(4kl_Z+kPsC65-`Zh#A)fNbbG34nv{sseU*o)#O}bj;Uxxhd)-*SUw^gE~KUc`ZPaQTzBdQ;XPmsn=VhNIHti7VTkLoTh zE8hp))+qGpLu(|GX#dsd=f$%8l!3jyy+B&cn!c?q0u2p~%Az#<5D{SW+uPeV*K>@* z!a|4pBl+OaP!c94q#s=1=IJT4vy->RpnLq<_w9lDYP%QF+4bO4=^H&Z}Q*-5SRi=K2 zixms%=={9XU9-)K$GfSK(MNw@m@>uW)CGy3lCZho${laLV5eez$m94%A6j0jUpORm?u!wES&sX}3qlg)N# z$dHf_BFDC44^K~GTG}Y+5&p0W92Hen$CKFti^Om)Uf#xBn3k57Hiuo1^Kv16bcH;D z7*t9HAwxsLudlDwR{L#M=_7Fj0=`w>>#gq9MvFKV6lhAyQe!pW3tL-TEya-gzVC(Q$z%)w8T$2+Qwl!mjG_WMmt)S1YNG2o16Q~Hc(q2Y{MHf zwNCA!AxirB3#ZGVnDf+EKtO=CZ1kKW0wlsz$dpdts*qVL?eOTxE$8^)fQX(x#{H3) znAkR(VtRvbIN(L9r=%nlQG@!Se^^RddRO9Dj?X|tS2s#Bj-Y>V5HyiWsoA;|iUhH8 z1f6O(Z4)YwkNr2A+m3`VBwwS>Zt5G*F81?nS$PJm1Y^?W&)r$9IqTV_YwfQN@^qt1 zyofugb0F7Uls6>K3jT1Ft2F5$$AkArc1miu)FDD8vLwRrcWRMz0`e1+f1F)7qJ6J~ zw)v66YDP?ZRq^1u><2k{w51$7evECB*a!2G1cjX!r3K*k%W56PLac zr$EZlK<<{jtmH$t$_v>MDVqYuvBhlFh#E}4n}FwC@bhmng;Kg{#+!$SuKo_!NYzGc zbxr9KS`WeCEyraQ&fM{mfYnJ_aXfT3!roMKwos`;5z(;v2e)jw1wkLb=uk}CuN=a1 z{XOSjv9Ymq`FTH}7rzDyJWA;w3dmQ!E}3ExS66mc1{HmM{9Xw&AIHJrC`=pjxqNH0 zKEA`J%YU{SDKkrzOV+;+4-ah(qT~9h{H8$OArix9FHW^Gaar>#&=l`Y#n~46P3P3A zWy*Dila8_CxlGBiu`~Qbil9!Ts>rBM2~(qFj8lt#Tj9W>h+O|%N%@&0mZ|H;=dFFN z!4ll-c^W{ZM2*Er;SN#yz{c&U;zTN9V#E_onxf5B63p8RN`6O$M{CDMU4>PUC*%u$ z6J(T&ih=sr3kJGM;roA|-eK_w_`D#M@>Lq+>crV;h|4@hlfgtdeobEt&obyi*S<(- zfm8Q8ARG{YQhu`<;W?Ivn)QL6tfvL?lTKGj=(#`wx2{T{CU24Fg3`N7Js76w&j@B7=!++p(Z~x|fY@*Vq@C1%>I<*tK_Yb1UrWArSxTg`Y^J zR($G)fpKtqyRvOWV0NwTEoqi2q@>X+2; z#f_>HD5+-(>HEO^&zc)BowCF>Me(UTPllJ%C zl!fJ+haSH9S`*Xk1g%)_ZZc{OH)*$%o{w~Vx`;%SSpg*{^emAb968IH-~dZF!#<+I z);pO(w~*)v&7{_@OePlQqn_^iH=;C~8ohisZ9^hx4RY7fb-Z_Xs((ep#=4B;Nh8R895wW%tv&g{oyedbw+#3Nbq@ zSsyTm)r&yi+PcS#N@y}+4wh2vP*)H;2c1{!WyJ&1HxkDc_H2?TC}X1*C36SeQ>MDrlzQ?JLh0zY|;m)ve1lU9UCeBWk~6|F?f|wL!223az3I z!#sRXrD92cWM?tzz%qBtl)~u5->2X$!`VdM%E`IDdlrj&+rELM(M<4SAoFwt8mMkJ zRFd@sLo2e5=a5Qd2W&|d=f&T1?k(e3`%K@t!}S*cYW{Aue-kZV^8Wo>_m2=OSv|@#d)&3zoZ%T!%jnopnSZSfWj;?phQvyMXzqS7m0<;VV{Y1}SW>j6P5{VxAWUCtG4g*FcbrVh-BD_T z$@<8w?{5V^_sOac1$+CrpCOI_Ay*TTQicS9;c?)umc>gaYcjUCq;&&pF{<>3y%a`+v|0DUEB zO$NS0&odZC>pvr`(t;Mrm``IG6seG$1Mz>LI`wO{DPYziKY%_Li1G^jsJEhH$U0#q z?Gay-3CndYB)7O&g((%XsbYn6TWoi!r5_(2w49`KQ6{&lH8&NkR`X<6#QURhqd&V{X7fp`Xaas5fJDr|SORH# z3>LG90v9#uy`bqT!n!51oZ-XZEuufGTGH2ZgH=2+p@>mYfBQO1FH+FH3Fjf{*eCxOr*^}LfDU#@ z>(@yoW|nG{a8{MXldjp*6}7u~wdgN};St#Wb8>8H9qSAGc#Y-o{wzC=)Ak{Bnma-l z2EJ2Iu|(o24fhciOtQF8^pU z9*S-Q+=H+FPB%`Klr&uTIf<-BNW`CR659*2u}NAi1%owsmdM_|)!S?NWBZJ(poc^ic5Cw`Cj1!|_t z=Qks1G25?I3(@(eTf0-Tz~>ubKeBONVL;eh$#nLj=NtSfew;CA71{!2OUA?1QwbT( z@%x8){ONV4zeCG0ZLRcBG4Q7gOiy-1x->wBMIaAi#@IjAxkDfhx|#F$AOx4n(!xvZ zmn_$#3n=1roXT^xgfw2y5@)#feaGYI8Y6L@Brru6 z3y{i$eo+5_L6NvEkz^nc#MU~m3m-096C_2zpU=Q2RtzPTp0CP?<4_dxQw2YO5u2Kn zBvr?o*`jpBP1ND=BUw-?f`e(Uwh6@#)Q5fF3k!5P>%j_R2(_0x4Ox47yCeX+5;ryv z(QP2gUtT@5XvN^a{z=@}Ew6>HMO)0|{sm!nv~x0sQ%!Tm0DX~9&y@^ zqC|q8B}cgwSs{nG#2a>($_H!niK1UOl+*j;*TC>+%zO3gzAMVYJ3b{j`9>eLZ2d1t z{h7FToTH-@mLKAwlUc7Pd$QOcFq1-;#9dFo*>ELVF}0b&;Ba|W6MY|TAhRUokz#V&NIYX?lYC?Qd!2N6ci9mx<}irJ;;e71v~sW-V!L_I=h)d z#t0v*Zci6$PX7((e%79EP}peHZ4%fl`*fwx5aH~*#(m~-ZLfrJ?63iyrYCTAh0T3T zLC5zPMlp=C)v;bAV{Dm?NssW^2QRR#YDuIFMJIYWpF3u^B#Y9ry^r5>?PBS?ZzeHU z*m@D=e`4!--Lf4A3+X8xM_S=*oTTjI+=}*4B+(5^8`H-rkJF`r0@tu^FW4ALcO)ffh!Z$Tx92Nms3d~Dwc-k>B6pmOgDe4VmAd5t;lIHAvxnFJ4M&JsnuJg|{ z{l={%Cp?%YrJ@2H|Bg&9hi$7^W(H&$>9oQ$_PUGRux<`R{N^VR9yP+pYy&b>naus` zk?woGASC6ocN3H#SK4&0*b$VMUMfQvOJ=kKwC~gQ%k01;eB3jj*_6E2usJQjB@{D4 zc1a~}?RS6T>4;sxh)M7=3LK#zK4EOuNc6bmozqVa%!kVwZ!512NRLQ4Umdp?m-9E+ zdtcggqhGbpQiXaIp9~5tLjZ?1i8k8WxWAHsTw=mcdF^GtH~ztnC{bV78T8r$$YoQ* z>5=TZ{I^C%ju`N5#&Y9t;a^X2KtMq~2T-Md(b3Z@D=GC^C|z&&&Quo`f)p1QQ!y}% zM55CUVT^5TWY->HSuVvIyWzk!S&=Rn6^XK{I7bS#qYh)7>-TCc&|+8FJDDk}D{Mg8f4`+1i40oHZIl zhlk*;U>=H?)D9`@Y+r4$%UT8D=Hj{;Nym`970--_i0~1Yw6&$X+Uy){RF)>$KR5^; z7!bjW+yeC-2A;L=StR%Vizub2cud_%mf+~jH`<$)Y`n%rDLyt%%lD0+Y>gsB2 zY6={Bm_Lj!oUy8q6!T=KKgb03;P5acI~%VjGa77f3l$xGI2zXFU<|n)yu`+L?xj*2X@`#>(_2P-w&M~A80++2y@ zzkfGKMgI_ewtnCqIXv_zp(Y;wrL7Jy(zMP;6tjw&e-IJ45 z&mWLU2xd4kqbJ#9rdv9CEN^IcppF-OR{MXX*8+qECK?8 z8B2m_e=ljmTNcMQ$dKURx@&b$EHXB_Y+uR}W;V8uD9b+$N0XCse10G4VR}|~@!m*} zdUOFJ`*Q^a1-8A+>=CR=DDc+NHq=*xVmoUXKe%0+)B!u6l7zv~aKY3nL?lktwIVBy zA_)nJ*2=VZ^$)s;!M86BUe;~J>-*-S*^J_o9i78^R0utN3) z>1{!vwv=*wd+d^U>aw<(lwOUR#I+tF?am`{XdJz+Jh_9SolJ#{EDUDmJQ@Pk->fdA zydlIPjNvE@mh5Zj1JX0v!{>2PY1?qU{ko~zBC1wM*V z{W1N_XXTh7b$%bi_FV^^d6eb1e)H}fQTCni!~7ESQej8QWbSHoo8S2qh+ zm;mM6oO`cXAG^;~F7KfE%n?J2P{XN;N(7R9(41gWlC?2AW{-EWJfe@F(%?U#h8hrZ z<$_^jc-4TmhngfvdE`L2W9UsYcq=>@C-15pqh}cH4GMYknkFq)9nD?yt7mC zs1_f7)Q``Z`vY}c>o6WOr}_XNx~^D2Y{D<2ID@(y?%*d9QL`YK#6AC3N9U% zgp-S2=*mJ-E)*}>$rfI6GdfZ4C9w$YVYnK4e{DXH zCK@7gq0}Junqi%Q8l$^i__Vzz2-=SB2WDAEqS9hY#6YabSQa{z8pd8n1zrue4A&;( zNck^p5!ii4iUkC(uZinB5DNj%vJzfsWc-A-IqV2;yT5Ikx2>#M}g=94w(=qLaXUu zvr@eC)vXb#O<1dDT0=!I7>cMsZ;tn!aXo+*b+wt0S!G?S_6V|P510mWB61k?o?$ugrQQ$27&9WbC-eFQm zrQB1}pjmz~8)RyNI~SR_tqy~WiX%6LoQ6P(G5WcS zM9q+h97bJYOYW5YF!zLb9)i%cP*p-w{+Ma`QKQOc0}A|K876%Y2G|`1`A`vH0#FP+ zgeZ`0*iMJ-xq8V_I``@7Nd0JSuPBUidg0D%H22@2brw|@l^JVJn2C(jE9*zwZQ-r56|+Zw(V^gSAQYP3w&6$ z9&ZZOHB&z`4L+RWHvh#=mgwG0$qw}2xU5R@_4Ul!Ucg3tm*}*O87|* zB&BBSKG=BOcD_IUtqtv-XB?y@$IJbDdm|$+q_t(|LBw@qVi+AAoeLWvqOe*+6zP79igbBp+%7DLPA9#hT{7F{ zncXrxi)8X@CWtOi1J@W305qAw8m1`p{(NnL%6tgLj~T#3so(Ad-e^9xhl*6ZSwH2n zzP>&Ko(}+k5+~m&`oE#(1^gfl#I|Loiq($yu@La>>nXIF4FGaWLF^L6yseRiE`Ts& zKy#X%&aaRmlo3(L!?y?Zm5 zO-EFL9_--*6Zy@?le7OOQFbP6AD$z%pwqIQ7y)cuT_3YESS>nG8QkaMHQkQx++;AS)vs~u`E7|t}N@> zd%VHFF!3vNVnW*_f1WiG@4#Spd46RU<4~JZ?B2lX(feuYPg@dPr{i3zr!3n{c4-nB zl8ikqBNUBw{s+mhz=dnwLmw;5rLsjA`WTNH8vCu%unE57wz9+}1+}3HeEK+bL7mi( zayw-rL5GVw!FBviwUgYXEuJ+#-Oq0LhM8${L9Zfbg^Y*X5R=;`-{Rl2T{fN71DQsS z0O-86)D181ZWBb#0CRk57evM@oZHl9_YR6?Hhmet#Z&UiLPW_WXn^ zi!G;4;$PR%&MjC5)uVx?=&x$*vOUig!@oe=qklq! z8`;xEWK|#Qk-*AX4RTVyeHhOQH`BwV(p!lN z_eK}kCQ|%DoLW?D1Ai!2--0*2#_1}i$ZPNbMfm@$bI-{pZF>Z zRU+;2lvUa2PS35e)&8L6$*3N)bxzA-Xzq|&@=2*4133CB+yY9C3 z0y9av_ib`V1cU^*JjO6N#GN*lxFU^@01r{E3&#tku=RoFD}Vz#LH#yC8iP~1-zRQ* zhJ#HRq$!K2C@-`E8V~hbebppwt^-wx@;2oI!WpkQAs31P0W){T#Lhfz^fp2RRa3|3 zCuM62K0B4P@)dk~Ro~T-AuZCq3Ir9ZH*L5hScPOS>~M{5Z?TK7un1OHbpNJ0`xlx! zKwW-4Xkdt5L$N4q=M07!5U(v00D`+Z%K|uC^O1o#Z`7_M)@>6Az}DYpo9^VF*g;i= zXTNtq0Gc!9>n?&7)T=IT50T6JIrk%e1ym)}$An>QJbCt=+0AUkyeuaNq;G`bGPM;aJISyp^i~YRH1lH12v}9Yx)*a%^PoDP5 z^t)INSP2&~>A|X8+@)xdq(Z;rkf2DHWDU0=^A&%Rqfm2)h=8Wu2Y4&CSgh zNPZO*v->&2uF53nprR`o)YE~8Ldj&!kps!Av*oq57D+7p^Vn6C@vBL4SWYk@fg!m+ z%!oji&=(*vX_b0*c6KU8MhhZK6!Uy2EN>HhUN<;3{i?{j`}>dsOpF@|Y*8PeyT?cM z*(8~C3#|||42;66s%YV}%gf;&-}m5xM>Hhxf;!s*yMHY|3f=`=QKiT{)j0o<6-w_B zgiQjdY$N&s#3K#pIXOAnAOziXcfcnb?amNvrBL=0Rn|}#L89i@!e~*XKEnZW6sG8I zZx2>a!TU5JNhVVENMui70M9V~;j^=|RWjeF3vGzlFLTClaB%Q^;$$ux7+efjD|YV^lA2M8OOYN=NGd{^V`FSIAm;z4>E#QBsO89$)Q zC?_VKr0|_&8ZASqu;C0eF*9}3UoO$`%;KJ~(XkTogS!OU0qlYsBtoV5FThRwE)|P= zUZ4F)+#h@6Mt&5)xH+bEyF>g~_|T(ogT_pJKDZ3T+z&4^`Ma)#1{L0cwMxLvF~VMr zn(q&*kY@dqEnwdKs#a2)A-DBV7hUlIQM>J{=XnU_(+LEDgUA1x)^P~=o0qQO-(uZ& z+-i~jl*>3_>yh5ojbHIBy2P?exyGU5#gczs?Xj_UJ3m=hPSY&8D7sai;$4?SDkokp zxyH#Q{m-K5Z_PR==I{R<)Wn4)nuE92#Firu56j#H;vKJ0lQqVUXPx^tFf*el9+O!` z7`}WGqPWZ@UhO&1(G|%v(X)d-Ep|qys&Y?pP6P07boMzBl3KnKiP4x`)Gp!kxY?=P z045=7Su_~s;Xa86z~HD5s#H!)lq%$SWyChU#>Y-nlw$C^2_<%SchkEESoXA&t0DeD zsW@6VuG;pO8H{QkRdQdWzXF=;Z!8x9psB~s?%hos0?R(~Lfm$L0 zuLu6oafidWl5l_0;p?dlDmc-fke6iTVzRt@=OQs;xaZnBb3x)n^MFCm|gwxrn$ zZml;bH(2#_mUbei8`@t&g@*SXKqyqFG4{%@B_5xl*I1f@0m;bs> zqJ~-*3kDCjEJ-wDj3O8iT)ODs&GL57X(VO^zxWN+lmpShFkgzR`b_l5%6 zY^=1}Gme|(g~Wy6BX=@TqudSO+S=6kGj%-ILE9n!#5RHpt|GyeL`eXO-NI82vPh~)nS8~+=1 zd~OsXC8beC210SL^ zw_^O^#Z~|O84&{LD~6W~Y)2CfLy1OA0V;rxpMNx~x&%e2;EypI4b6Bw2;>V-aw?FA zvN@6^RlP*WEk;Y^cXNg17<4=jwT}@EIr|N8godhNug~jU9Z=BMCuhkPk&;<`6#@t- z$lrju?NaLUU3LZ_ZfAZ@9dx7>y70Dv??Zk=xoCNg3SN`=?MYuZ#F^xoG_{xQ8NpRQAnb* z<#a`);1McwOiWCSUh$RDEZ59A-(`YE_vw7vZ~Wwf8Bv74*M-z`Q5~P290b%e}ZH%P?_})T-9KNPRS(-z^et0xYzCf z$J94KN7A)h$F?W7ZQB#uoY=N)Yod-daWXL`b~>2Ywl$gH@Av)gckf@TR~^ zdb-Yoz4vpkUCW_U;1W6AFQ!C$A>dgc*B8x!6;PEyfRT{7S_x34IGF-rSQ zAXKzxfGQglZ1*aOlxZaztCmgvJRsVypSxZX0;P8grin-qZtF|we|aw)ZYKwQUU=}s z0gPp2q6*0>>g)Ty>g@R}xliW!@A^ESXphl4f~~HsXdsBJeHtsATMll375i8y=v?_M zVWfhvkRNVWZ1p;gX19o_$GJ|uK88BDeu-ZIPJQoMpA}bjHa3^3H>9RT$$QiVT#zl_ z#xdTHH_VMdp*~5@GuGX^Zz&)TEgyiZ|qHj!78p)3L^qLoHOtT(tkHarC|9{tGQ7P63KkJ+rssj9NMde;g#-}1Wd z2zsg7{*&)_n0Jp!DVt0x777K@WauO3nzfH0@jr2m91v^MhzOWLmHBvTUF9|~4U>u< zI8*n+!s_d*ZFpJnh=A_Q!hV5*)gl{ifMX6n&G*l{n+%ytK(ZIY6$(gGTyZCA<2`8~ z5#2X~Lg5SF3pPYMs^r_x9n_WD6TBV=XzwqBP2ZF-nM)!uo35jwQ4(g_SEG=o(ZE-+yx-`?3lQfXL2h-A-XQ>Oq zT7QdsdU$Zxdib76-OxD;6`i3zJ2fR~U_f{tN>a};lGSA|EkbJ1hQ<8FEm$XAmJ&mY zEf1`*EV~}8am?1n2AnC(EO?}Tar9V!T1EsAkcH9lZK`v+MsSckx;i2WtQdQSq9f*~ zhgGn!PI27linr?n1Mpr&A*nR%E1WM`xsY!+$*CtUZ5PYwzfcX zOAWFb9fz-PCmFvg{L%h!MloW3(B2?><(hhh(k`ep$LKo6lw)f73rYQ6m)Bxss&gx( zBo=Ryqr1ByCf3x8?qyp_#^EzO$MVa38pU${ z)>ddm>{z`=Qjwnb5tR2Tg1ZZC=ZDukoRf`Z6FVAghU;!bm#&T-`$921==D8?X-^EL zxGclWkw6p~85ySX#|?`3`?Kov-LK7c{TtqAeu!?ZxjDoSBRkU@1eJ*uod5g=3{Ho; z5wHxb7mRENr~?>?y4o_?PK3gmugQgn;C*&Arhww;p|;#VkkMLLvB*eB4MCrQFtiM- z;Bs>#i>(8y>0X!_RAjQsP2%FEWd3z@yMBlDWJD!VN-36I2H*H{aD&^SKp=NVm}n{0 z4<`9Bf}5QJ6f^Nfbb@kNr{`5DS#)XGKA5e>hzG*}(Qm>rsCL6c2BGB{vp2T}BZPj& zxE(pnV{%>5@ll>P?ghxoZL9489;ZpcBlvE?I5!YzZH<(dCDFN8h&z|;1V)6(Uz=Sr z61Ce)1UMXhm?O!+i?4>K7(^e0q)Qg@UyO~Vv_iKZGj8tnP`1ypG>yJyI~f`d^&V9y z7Y3?J;)kAusiH<_dKy!NHCHbwR3>L|jh#*L0-j1xQ%}8(|7*<{pf`Z!v8|5_M}7Ts z-xR>7-udW|_Hl`)#`B1h0@G&G|TN=nB$vxQzdvd-1r-Gb=& zzPLw66}O1I-$#{NZAn7BD{N6%!1(z2`&V-qvOGc%k_-T^LOi-TKS0^vfuR@PKp2_* z%FdVIrItvc>f{T}5FMKuX4K9_-O}=MQeokOT&dZ!XB~b0=1>`!S6{r4lJbhB>N><* zZVSxY$W7nWT0c^wmR_?($YKLX5fNj@g`X*BDi1-6?v7y;GA&;KS-H=G*w?DYx2`-= z)1CPy{Wx`D-ZKdCzM=-?PY>M#85O2Mg%zi)I$fSjEv;PR_#$Ab(QOyTVlvTL4iJ!> zU(xsI;-p0Y>JGFCH=9a zrgLqpCw_igtp_(t9#Q^{k(4iZS3IVz;x~>={_ko=dGgun7T8Ib7yTYo=(4mPhhQ~Yqx#Yz`kRkfD@QY3dQAFr2dc_ zuQb*G`5lVmQ77q5Bk~(U?auc-u*-98(~}GY^Lq9c*+XY!`jZ`G0ODdo`^inTKT6jL z-o})Rm}m;Rpd06IN?GjWD!+4dbL0%Raoz*-=p+g;L@EY=VUEQF-GksGLJLZYD2x4R zkj#STBGu~`ofHXd{_Nm`7S(tDZ&0*_#=-n>$yN$NQ-e->(gk$PO2opm0$@Lo zfcryvZe?T;&bDN3T%Zn}SvAQN;{>^@p`<}sCCV@lK?(X)eEJZ>quxEQM}1mER0&F! zJY(Nd95d{I&?x%47G)=XL4JjgF)HE_Mdy#j09`2#%CA{qzl>;YzH1)R8_EbcfyqG| zinj8!L?h31J55x9F2Z!pBp}cSUSqTGkc3pGZvzD$T;At9=ibAf$~P;+!F?5>Qmv7X z3!^Yem@H9YDKtn*NXVO6#o&WChy&tun#>lj;xr5oD=!xQ?86hl_GOx-l}H?BQMP*Bud_fG7_j2d>mV4h_fzq+efOsap0b;+7IMKf?_1C#*eGNE97u zkBLY_7rssw3))`13mqb+AY@&3fgZze+6fI>qocoH%d3_3hjLz1V9cH)NweVUJG$D zi-o0RUN5TeQDFppfUzdj3=~Vn#a7Dd zpMmOZcHXejJA!1qZM4f+7n7^uX@>$u_d4jX>`A6>3Rszu2f;g77$ckdX2P#RFye^~cs;F%APIWAt%b*=zF~o-qoez+qPeAkUu7f^ zi=I~uQiG{TB;ZL5D#80jN68yyQ%WDONh|F<0Y9_*yd6{+bSD`L6=d* z9lQ?u{Hy5H^Qa|o4K|(4R$?}hPWR5KcGz2o{HuzR*Ly#Kry%wpS69Dvo>SMSL_%w$ z{9f1L+ZWxLi!q|+!(2l4!}Hm)EXG&i%lS6FU8j2#px%1RqE=ORYi*a8%KRpx+;Wm* zhHGX?w-~}g1Ak?g)+6IQ0?A-~+e!23w3_axiKT-`(=g;%9f7FUCD^>(YqG)bxa=m! z2NW=LZ1(IeOo{0Z`Ja+Jp(4rLd6UT=PY-0*e@O2NQAv~X21y^u4sBS>CVk<6Xm|K9 zDTdpqxt0911+sVh%24@}$!@_2a4NfT;8@*w-x`Qx{-$VcM!m>?u{H8+u3Mra>7RTe zrp+ls0=WV*UL>(};__p|2@>8@q%Nw9v&FQD#k5VOh`Unx7f#PLKjsg*!CS8EBfIm# zGI&r*;&;+hzv4%#NH)0A(O4|8%F23^OAsCwJzWLm`5`<`3qn6lO`yJJU&MQHYBAKjXG!ZaE4*%uA(nbV}$s0;` zoZfJY{E~_mX(lNcACI)bUxO!_@CrMau}re(!JR({j83m~GMC>^Rd2TQHv_7xrSl!U z5;I>{W-2#$>NBe8-1GPwc*}+R_wFKz#7p#^6?C%EpR#;E=l?7<7`+J@%yd&JBr4W# zEbv7Ckx6@jJkd}4_Qx%T(3ei4T@~Db1y*n}c+%ayqJ&_z70a0pd=V2CsAy>a(~>X< zj0vcGR%LVgWl0`C)N+6{43Nn?<{uqcLyaU#6$m!5as488RqJcrxfaH}1*Qf}3|iV) zWvI2uKozn}+0|`EQa0`=t109l=;%BM16;2b747aO)~A>J;TSZpQ_8zyd|Y{1#dk^7 z#c#YLQCL3U80T0dNglVIx?Dh!qM72ws-*|{AHX1OY;=MOt?*h@roJ}jUdC_J_9>(o zRqSV7@KB@Wg7yJ^Zl_$FM+H(g4QkqQI}`WAAF*$nzyrC5@;10cU#X$U>Q@LWq$h3@ zohQQiGnFypaIrTaSW-ad63&*MfBVizvDXVW*IcfargUJITCORK&$>AnyGc7FWGbOx zN7t7-SJ;ObNHQij3C$I<1HXW@3@?ul>E8a^y7&!V#;OM?<*(?JvlzFGxuT^761r1t z8X`bxc< zf-Kn^6-))q`vNWQVX!aPJx~AyIJ&Hru`tr68?=SxXDa zvO)=|vogDo{9z6c$d@CqbK{cLWw?D@_>-}jGQy>c@|C%l>;K7Sk`FvTg!wJ=OpfTJpIhqqkk{Sp)1_;Ji)Y4IYC z!@oJN9J%elP}J~2^&214{+*(8_2cn?_B-l?^NX$KYX=LaO{qKuFTsCSW{|5Lq{*b) zbb!jw&p%q_E=i!RA7%@5JCy39{*0Na>9E@nszYAR)OJ3a%DLyRD(dXS zA0HoYXle16Zaq<_XJGK_0Ht_rcGjF7I;^}w@eF&Am9_PXDoEt)r`>WLtzH1{*zZ+m zPtTDUOq%|Dx8-&}aBy(5I;Ll&Lh`Y#i3$0uy}e?&!{6Iwo%n|E@bF`^voiAX2v=J@ zh*$VtNp^#+hXuRHr1XkEYOAVNLHJNd$;kl{VCU~Zy*>oJlgX)n&uKvL&l#P-6v^gB zs18Ad%1cl_Ej2kA&D+~M*fTsNCI(*MxX$Ido!dbF>dO{baRS6vDKjTX%E^PPMM|oz zuC6X`L#74^HCY?jkJu(bA#i5T-kpzG;b+0-9bDa2KT1V!;d8WfzvwK^oi%I zHLP3GATZ{<#tU%ey~|~=pi6iNyJ>RCr9qzu=k@D{gZF!U5~GxV*63^F4K?c%)Ucz_ zu}i}yEAePnP;>x-xd7<9&D3YKeHVO|;>#2dMH1i1kwKHg!lIgoFPu0D=Y1AUE=cv@ zKEy8?$t1swb@oNt@-wJJ_Vh zJ;r6NLU$5wj%eQ^J`TVcYDi^h6$IyAV=WnC_Eh932 zS?u;y)s_p%woxO@xT3!BJ**FI&9NaJ?Q9rOu5e-FCm_hvAce| zde`(y7uigXY{F%K=>|iR2fMW2Q82(*i&Zv}Er=a$TQtyJNJcqei|-sSTXm-!5s2x*~Y!qUB^vI zTPJ#RCEdMP3-Oj2K5*VYEsXicpAuO#9z!_z^Ere$?sS8mEbipwG}_Yz3$cEhHpFadmMa>E|b4)ayS^%Eiq+Fg=~%l^i$B7>Trz^+an-_Io=(NicUz7l9YrJmyqb7W4U>Je3#1FFEE|G zgGcjdMhA1BIm}O~TUuM2Ag?Ma3h4{xwX(Eqvhe~X%%fvsOjF!HKLWTz=y`eZFk-@I zgQef%WQ*ShT~jsa54lLWUG70S{;{z!QCnO3Nx6;inU@#u%v`v1G_gJ$sr3FFy6-Q@ zl|OzU4G#~~($b0=84x+Z623@fNofxXHtvOYgQe-8M=3}upOXljOJk^s892|51~ zgRlb>)Yje5oC+gO4``4>6w_r;8-wqIL5PI5>%+YtKBaxG$z%6HCDg9md9gpx4G9nyU^`b_ zd=%etmNVXR7Db(>1lb)_x3nbD_hFXJ{PHH*M^T%m1wt08I$0+LW5>RmTy)okBWGpi z+`=S#9HdlNo|&A<2l5u0$8fAxxR8sXipbJ1@4Jux&{a}v`4>B$bF|CiU|C*XZRdNGH07?ahSiwAvbhEy z6N9fHBb6{;r;49|J_K#1qKXc_3N^r{H7Yb%(!)_w3o26oaWq-&o;;A@#Z>-x1vkXtlOO= zNWSqtD0;p6c8O~@t@As^wx~qj+>YwsHuFrSpuZ&06yZIqx#eD%@sY&TJ&BDmExCBK zcw}Zjj1U8}xsj(=b4}YzrQNFGiHZMWJBBM?V8x@YY4)kKX;_xjD8&7m@P04=7N7d! zAUqLlW5=Tv=Dv@YKdl=MZfer7Js9G@I+m7{vEprIHmgTqH0E%O^H2FYYTtD70qb~( zHq6)_lW za~SuNo3ZDytPSXO5hJgasn|>WRDOe6$F%I{{s2L!yJ6eZk9bwl!#C+BS&i?%{+QDN z@Yy!HLcL#vk!Go6Ig}u;GMn6s?5~TLD;|uwIuxxGkzE%+g&RC7BLvjBp{jEEA8FLV~>G5 zFlN}&vr8WM3aL~bP%hNv$_|B6p=XkTtW$<+e%WaH11^XlE_5rr>S)_^@43D~@RhAa z>Td&?DfpHvE>nG1^fm&OGtbGwQ5vkLS;8pQ$^pdEG1#QLi6P9*p>F@43Zx*RhWGZd zrp|Eg9+UQ*r0W*C{O(_<^&H);!s~p;4~N$lA`tOkRk7+9VH>@F=QBMuvqCRHlixzG zv0pbk>n{&NBR6#EP}A&Bx*E;ZG&=C~g&?x=^n`SHd2Fn7ynli&>(dJeu&K+3J97RVv1Nik{T z#~ksBQQ0XPAjhwr3Q(21?O6WCx0+52x>PUar1ckyirYqTribgOBtI%KuMG0zLOLtj zW7;&@+QqD!Z_d^~2s>iePWT@gHvATu;H%YR0n4;TSS82D)xj<2mA2NfkCb#xKVUC= z+85>{!r%Di5<{B4j`xLTt9N!HL4vLU|D}9U-Ii7?-59P+0;9L07m8oF=q7jIQ(;p0axZl;0FjPZN~@~m zG9^no(wSoDWpL(KR%o?nKy``I`#C!esrj!B84NL*>68+6IAXx){u)jeRpMc(WeZbI ze>HsEN(DI$t`c(D88D_QX&c~W$qX!QH6K;b5)eo_t5NC`JCP=t!O>YNO%2iJ#YJ&5 zh4NCqTHHXb`0=B+*QlEO;NXDvE5;+9gr8p*r5G{z%Gxd1caEoM{_BvEKY!6Bi=`<+ z-u>5V@^6-w^fL1xwwFi|u{oe>(AvQvgKANzJu-M<9Xvi-b|zndCP6vyD=qvP3pMBC^3BcepsL*2LoJ+e7vfxp(aGgG zF~g`VEjoUTy?EG9;lZ^0vz zLtI*V1gTT~ODRjThldB_u?0{c-4ax=z?V&Y@Px{|XRoxiOwNN31xc~~`TX@Xkl?9* zzqITy-WaZUq-QVK(+Pq(Z79*^6AkB%II*m#6m=d%zTh({Ee`MMI53jeLl5wI!D`Vk z*64UVB<)A2txCJTP4=Xy;U+mbN|K*ia`yJ1M^sLj3~AZyruJY?Qi#;3l;^!6d%`kz zlK&Ctutn3xMyVI4@jl=qf0G9*u8SfKrMLMS%O$)prM2bymw&mMxtt!$<+omk-cr5} zUkkNO$8+IcmUj@b19xe!tUfXJ#|@o(RMqtl{vuBMrry#+tOO@p^4*j^SUZd?za$hx z1mm4=ID)u{i-l%&_3$4%9FXER!2DQzw{QcYx{fXr;x=y$U@@QrO@8#O)W`6~!Z=*x zg{*x8bux*OP>+zCMGo!${WF-aIw^z6=CTpnvx~h=)Q?Iuvy@IGe0p@rv|yX2AznX$ z#Z1ez{4eZ8W()hs4>PL~9Bby99rZJmZNx1V5bL#K2y$zS(BJ#N&z|Ccd`Lgsfb!Bw$=Ew63ZAkk&_TO%` zA1+VQc=Wk$MSP!JjGXFhn^zt|zu+2`xavRM6-kwrVI&4boH#grex?EfL7%Vu#?$2~ zX3LB}W0ga5%9fkjc$MxmzJjTAU6v5fXf9x|W~3ajD*OCa>Bb|MK|ykw=Lam5uC*YFR*4R%^Hh$GUJg| zZVR3ytimOy)A&L0(?0j%!**^_;*rTSj$C(j+svCZr=hPIe1Lm#_dsl>Y8P6}lC){F zQY5@;&uFlD6>bj`HxT}Jo<9%_#9bGwvs0IZiiUkF8ZdgAe?lo0QN5<{Mq!_NRX1~q zS5+ov3fkH^kz8G#k5107%^^RJ?I{&$HqH8;fh?^8pC1&Q5{csn~o7dWZDDxGZ ze;(YI%`xius-VyQgGMZ?*GcT5EP^-i9dRhVK74;Qhu5_ZpD91!CZ3*1%N^R|>1qG% z@$U^oUcy-1B!Jl{I)NsB*RL3Yi3p||pw?3i?eXwHak*z`uecUpuWmJP_tK?<-;332 z^P6!jCv#=LE^z2*nL>U!n4xhJ?b<#b;MNA1sg&Y?zl*0=zr$c4{wSIZCffFg>B{bC zfG>=A6eHP16fw^x$qPLqDW$G85%U{FxE8qrmef4MKe)cmt=^ljop4{H>sD{9zoGP9 z3|{R@QPu5I`96=S35^H4yC-=UjG4VNdnf_^@2-QaGO%fAWW*5^Y}zEm#^OiE$0OlP z1*P4CC{peVAgMG}Lqp_Rtz7bKlA>zef8g6kN0uPvJ5Lx`*ur^hPC47w?_StcAol4L z2Rl0m91;g&{OW`9RskS=I8d}pgdrc5xiDL4J3&uRe{ksW=g#7f_wE2R2-OVGq|E_5 zz#5&U3qZnG$h&|f_5Oj8fgb14adBxQv^+k3eokoeQUPZnCG>x`oL`4M+lRNsbj*lR zi1^)Ot4C}#cy!^R4gFi40Ra0oH@C$q{I;%xT^GJy!~;DK9%%Cb{Icb{JEM~ zJ&Qi$|C_6$>##!ga@Ba~do@c{(NpZHphmANG0psA{Lg_#IdzV4IXCA09Ce!0ymE{L z8hSJvjD!>o7ko~+nwS*xo*{2!GbU+t?BrZL6>teKF|1#3DRqORdjCx0AYeqSl{UQ+ej2;el|c4FHpd77<@)e`qX!$p%9`V)xz5SSzlo-aG?lYmcq zVqd0CNPFUMD@8*qF3c^ISY+egKF{X(KpZiiMf}RQs#oS#P;rS0z4%p(;WSNNh2liA znc#%*RPxV0^?wZpG=W|tvnO#9ylU|Hu(S%|9}VLr@%Vc%I1~Yz@Xs-2toIEV#QmxG zQ`J^KFo?+#L#gR^{c5a}KiV*QvpVGeHHH5egqZgQort5RM!;>*s~vlw)6v_EgvBp8KA<>`G$0rjGx?wq!th19iicWz&Ag|L-_htJ*1 z>MQ(7v`39g18k26_(^9grlD6c&@dltUlg(VEZn6b>}N=l4Ts6ebq%kX`j5h3l+_l3 z*L6H4R{ok$VBjFpGpLM8=S8Jg=T~&NhSP%OAnI0xXgD}wD=Ag185IZ-+jd2A@`0%r z8#ZUK6E}hwBRFvfj8D^-*8n$V1(nAnm zpvU6A?J1^xsOFwIJ!oP@M{oAS152evvCyS~AnZ~X&zdjn;~E3_Tmc8OpL8WaFlX3` zVR)E$!k812cNVcCPouAF+TIbAm$5(trN!VlOh3JUm`=Amr^li(zNF z5)PeM6$o&vhl7PBIO)uYr=%VbAOs@EBqt{W-R}!BQrf6(B6&*T56;t7se+*xmif1e17PlpdjrsTpcdQJTB?TQfM_JqdMr};46_{ z&r5tfG>~>iL*Tn_Npo{^?mT7|i}8~g-q_@%1W1`xO-&6A9X;ga#JZul|L0%?B5ups z?fw0|nr{ep%IsN{P|%0Jo38E`5cdc~y@EkSO`7sJUs`ooYZ+%|Wo6}{pnyb@?*f4> zo-9d73iXtgXZ;`B2c3};5)w=VY%&JL55>CbT?fymzrj(5g;Ha508dlKhkBpB#ArF_sDE{$U0aP>p4bz)3~ z-$yZ52t+XksV@2NR6m4wb@{cYrKN@3m;D+Zwt$kbHEFfRZgJini0p7XPTbkBadviQ z1$p983_=6JjG+7SaRF)t!icAXsgK}K6sauyr!)p!cPf|XS{LS96;#=ZR-6vo>2=>{ zApg4D*400@=vT@;?#L4{zA5j`nfGf;AAm8Ryz7n)xL%hF5>7nW z27*x`3-J5GFK<;uwT%GDWJJ4aAA{vo!~TBhmDn3sKFNIIq-4^% z!k+i6BUD)2v!mGZEopgx&VwXD!}#26vlaV#bSFqbX|Ep^zOTj_4B!{)D>^N_|F}!yEY#yAp>2dasT(y=1<#HSC-ows!~FAtp@8*u}_<7+&9O*nM!-nOzgUnMe<&=<{EHwkhM$iol`tBk4{I4#(h)vNS(U z{F>dzL{tcEG-Ot%w~*;bjCZ}`Q1vj@wA0PS?1#`GvmJw@-t*FF*CKmfmyw0!V$b$Rf6p%E^uFAHlnQ;Kwk`aexm`e@ zqJ5085>?zgMPfyxZE~NMh6*Qv3ae4~wcL$uU3t<(+ISP+>0v<7`k4k8Tkrco&aB@I z|4q={;@0>5&2b}6UGX`BFB?B1C0?V%OonF`adD_WTYF1?8{lGen3nNZYb33zvHiw|IArO;V#;UK^ z>FBS|13tqx(gIWANCKO#Zo94nZsv`*{O#f)?o&mv;%?UL`oKqNeqOIlKJm4PLBaQj zKCZFwgzU`{)GYby$UuZ>b(CGJBJE{~VmaOX0jbjPs!xIYpltb{6R9MB5De!oVKW55 zu0025K3ic9Lqq+~_JZtjgjDJoa=(b4Dx~llVZT`@28zyT$nD+ttLDb8dW^il`U80Tzrht-e>1+=#Nb>O9n_F!XNg& zK^K3_DP&`o!+(oGEt#Ix_{0SOX%$?OwtQEL=cXpHPpx^hu;PI5A8 z!NG&!3Wn0V%RWeFq@=bgI&&m?cz6IE3BrzAJ6)TRzj4&mIGGZsWv9oy3?XS~{SLw3 zV-5-dbrAqAxS>8iCRA3{%1Bvot)t-pBO6LvAS^5nX{qe2t;v(-WC&w0aSNqC>v5{9 zt3fk;rh5&_SgI*WNKXX#`%l=EodMoatTmvle~vH?6RimF_RnmAhlr7pnr1&fB8Pr= z3){y8X|1ibs97B2Vl`@yEiSr88r3jxR+meIwDv)uC4eqIAn-{=MI|^d&wyeS*P3&P zL`YCjBWdA&;Oz3JOKKB{P3OTgS|2U`{KeHeBS z4eG?Ols5p9bP)xhI~C1XJ@V4@yKIeaIe%%-O&r(X%I+hGuQGGmsF9bdo5s!00~?LG z5GmR~^E^5YJHr#Si60#7ghD#->DRNgVjPu8_o_{KPTdwdCF?=la`ijX!G z0coQ!l4r+~j@_*o@Ywgd{@`Ums*xp*xrRtAuM7fzVA@&nnpd#&xnKKGA8Ly$Ou3BA zfWduN#|36N7Xpw5h^VzC=;7<#q5JvjYv(wG2+lX~4VPI9|5w!j7`Km{kUw0RysJWwSHQg7RGOkQDsGUy_|9QI>iyo+r*FUoz4lUjvd@ zQ#F+YtSI?#UMvaa9fI}Ce3*&MOrnszR6-JO8^6-xXrp0`Em9K_BE7qY<-Hbz_vjrs!^G(&1=86cF&2y(h-^l2e9-BupGf1Nd!_HaPJKW%4vcI%cOjs zQ5jo%9>(-hgHWS4ux|_CtKk*N@!qlfV#>}ySYGc_cyWldUl8mhr|#QxB<2-}E^!#N zRuFeK!3q$h`B1X&2U3Yd4L~elG-qkU6&0{1JEEGcANCeIvA^C&rJ ztxvSyK61t>(BG6q|J`UQaS_HN#P6&k4*bK1egT1K%~w~%SSh^1!o4?lcY#I8c}Zz# zTjkXppq?SgyqlmF8dkq8|3FiXO|u*slKsJ0Mv&1~nS6Sgyv=9^!Rp$5?y1ep%xJG# zC|EwZ@A0e2d_kvBzUR;rrz6Aqz8-}L&Le6R4(cul!sby39OF11VUe9%l3gQ=f8i9_ zclcfyva%-@e*Cy+CeHA{vL)(hLlMXE`U*2PHbpUcY)Hm8$X91q<7Oa`Te$1qW7BBp zhfS$CKuc>ZuAza2g`MF{!IbM2aS-pejE|}t#WTRt>R=NX>7j%D@b0)@T8>FbNND1` z1V>l3+RZ>|r4rH8`vv~~{?8Y+#8nmdnXvACMSlw0xbPtb$`oGE5!%nyF9~^Ww|R=* zTndnrt8Ykmgxn$$<5aNQRZ;_sY$)SAO>(73AK0m1d2yzze6gAn5BRs*NNvBz_M9!> zwkpu(7T)%>_OxNMqEA?%6-@s(GZWqJl@|)x6*i zm~{GTDFwn6X_%SqZN0p_Xur_Uj8ZCb9hS23@a%q5D?G^ z2=rzD6!Y-)iW#MS&5;xgc*W`)d3zJ2bwYk+5Xs&rNVg>_$j^WOgfbiV@4V)px=l!b z$V+*9^Y?t>5*AL($@xMJQt>3DrltnY&Hd^ee0Y8iOALtg$x3@ls`u{9PJH3P*{JnvT8p13c`W`!Uj#rXnL~KwA9tj>+vq*MAyNNR6*p> zL2)HtHX21kJe`Z81aeyG#`om5w&#ZAwDP=%L~~E~SXD&dRvk^)m?15Wvqe_MgHk%0 zfL}D2E#KKti+P7Kz3HRC^M9b+3)3*wp@%fy$|)!-HwU0YTP+bKB^JV7Twe0rvC{;I zvWbpwFq5ucs`ZEU_nZ7ADK_K)wWJlbx`$jG*%OSpr%BuiP+FUvy?&;g!qpgWoLOD{ z)ApZ-cwfHP7`G6|Yg<1CONv7=7Q>k6I0qd0d#or01%(b)Y|KzE&5Z(Jc(a)lSP$Ql zZHo={O2a1WJ^ZmsFAc3$GBGhxBA!eQy3YoX5D+kihK8_oV%*kNRy=rkc(l?%t#P`# zy2$wW`0OQ}Lt+>+8D90+Wy-LHK@le$&A5efU%#T>1*pH&3++R*I3n<*5q;nmu%&oY zs3`5z^2?Bzy9}pRP!XeEQJNL?opxBsNu(zgTpH-Df{rj}}COv+G&n`_8q8Tx0M>%>MD)h5t{RS5%Ch-B^@@;nLMZ3UZZW2RBmS7K#Fh&PDw6Bt>mCjI)%QLTYLVdBrO-Kst`r<|Z|MJ7)={*o#VLd*D!)xj z{#{^K%L>%h*FK3f)x5-0f3pN5eVUW3(+f;bvsT6KtLW5?=GZXt)!vJeec`$|P?8oy zIv1d|0e)COu)IxVfJV^PE8>`&9VZ1c;H7VAtC`Gu?uR>o1wT-l$j#ns**mR93~PQ*+f!MQfgusq`%sjzJ;c*m@MzKY5_^F(vQhT zyJJYg%P-4iLmASB$C3Ty&D88jO$N-B05&|hjfYmnN+iW9Du1vxS5?7-^mD#S*f?$X zPWP%QlL3)Qmzbj*!5gpC(^rf?j3hH^ru<-FV011yGM?E>uGC4*i&H>tB(w8;z62D; zSlHNfy!AxK4m9iQ>1AgDsc^K$$X^|M9sZCcvfM|Hj`qH02pYCv@18tTr z6Ehuu`p&V>K(N-@U%X;|(f>7;y{u9ugc2&MmFD~Dcc6}3d6jhdA+ho0-z3&X$+#1VFRZDovPaJxnU%iQkOP(Gg7-1M z6eSvG(45aEAbAu6s!3cr@~zMFhsF-Du9THZI;=2?avY*(EBsY8@dxacgdf_pKSM>B zTtbo6^-pn~F7Tz?Jz+)T6%_zwPGCu3mg>sj+v(g`nqCwP3aGu7dqr<_;3#wWC)agS zI?@dIV{XJVBEyS=Q8%6V(PS8Bzb+WfI9yeL>>-Hg>8?z1p?y`>G^4mpVO%5$2^)id z?0(~Y&ly~~z!ZcrvA2n6XqUt*Xco70p|hnqavrI{63Q=YxAnD;+($gk(j@+W0URac z+Kmji!z!=}JyUZe(i8MfJbE zfKzl6syfFpH8V%SdJfg}*)F0HzA=TU%(jN7?!iayrl&S?eB!c|yivI0aWs^8Be$#^ z_JJih>v0QuZbh)XT!f6GLR4g%!`JY8Tr`xhYx{H!!)Z(KvWjx-vj_gHXzfdpu!ID^?> zN02Z}9R{?5@yL`7Gc8NKP`b_ zO>qoq%VILpAC;op_>*!S(iNL|lpKTymP#ODmON&zxfTQyW>D=U22{i_pec>XYknv@ zypcZw^=XN;P6WDQ+Rqjw%#z1U2Do7=KAZ{DuXF-^r;g*klQ{<5?NGe`3;w)f-KvKy zy7c7mC^-xy%#z0}!i4FSKZYJzQ9OtWM78`86zpBgpCH|)uy0k1+}^%^MXAZz0_{eTJaszy|xCJjtR!d1=SR~uAHA3*l5 z5Bsxryf4b}nUaI+R~P^Ig?Z5r>skx;ema%+?Z;C&yFYBbx$}MV%x~UX%?Sty2nYxW z2=ElKkVEbw0sGH!0xxnR_@KL1vI5lNWwFnFmoX>sjN?HE+%=O<;bOHl>`F`^7cPb! zwp&a&0ggKs_@ldef*f3`tZ<>~9ONTTVyE3!Y_r(#I>#5(vt;+Eq)91p|A6S#AKmGd z!uiT`aH_Omp7XW!=7p{1>v<$jrtDYBsZ#yMd0LQ-Ij+quLc!QwHBJUj73OfLFoW#1 z6Zpz%)4~?BFZm;mT87MC`RT|QfUc^@S_V9jJ1zm*>^!DRtomzX0W?s4C%0A z*lxLTVYBHMBo2>IN|9KhkRXn;*&1-Gv4vf!3DlG1pp~Kkn^GfK-!Xzzs3^9b+wf$Q z35ml;q@qMulIK9{h60N-4&|n>Dmx3S(zB2V5dj6`ryGqw=b1P!2IG}SFYkk5j3lhf z3}I2C5AmR*pkVxTqtUw8IRTE_=dpX`_pUqXl{yBoz{8+m{B(oiXM#9ChxRuszqZ*7 z3dZy6&weIMr28c(7_a=&V67mIfPjF2fPet6(aJd$O@Hyf!U_B`s_D`-ipdgroB+q^ z-&WmurbszYJ5K=`H|3y`CJpNXV_4)EKs@|N2FGXgidA>&7AlorsqsWo%XP#zM`g8_33~-1k@-ByMD>wm;qjQnFJy(2^72yh(5n>>m)HJrdN zv-b3duY9DoCYlrYKlaW$D#|PGWcTe8UrnzS0>c(i+tZp>BMonULH?~-VQPEgX ziWpQ7!9wVY6zN4eQlu9t(nLWJP&zZzp*LkfkTNhJAZ6zJ{&)_0NH)ply!mGj&+|Fw zdxnw8AM-iC`@1ujdvADnczAetW;*TNJv%8%49~xbd5Pjh!BiqCzh-z?IlrfrQ1HFf zQQqXboKGnu6#Pg=D4%jw&M69hX0*5@=GTQ0#B>eHF+y_0J)^LT zYlTO8KYE@%0|^2%)6sAeu!1VvWk?jTlGrL48U-t+_o$>8<4oOBomyq6%aWlE4E2Lx^T_CB7GpbGIQ zQcHVC(lb9ZiCH(?|^6hXkYzscM{T+7O=gs_C zY{k)#X35|)SzFJLynkq%X0UM#Qq-@nVC6Wa>A?1m7OYY1nBQ>J{bA;TXV zpUwvsv$|D+HerD=BOp1yVR*jFLQ|&u9phhNjv>SUZJvjRhll6CgoD<1mQb812|to3 ziaO;Tt_r$(ue>c|V0bJ;`aDAvN@TKtzq=@`5ox}(S-1yheU3=aiPNknK?N*nS?sS!!YIL!vc&AosBen)e2Gn$&3Kw>%) z5)$T;sE&w;2w6x-h%dt>%F4=&8yg$Z+uMtVh6a$Fr}ysNlUG+)LnIO*K0aPfa-LFU zp`)V%w{PEuy}i9L!xgA%OsUt%NfIlhuC9(1MS@;Duc)X12|__Z0SpZdp>$$&s0Kw)7a($do4=jVr@pde61frcHjv$NUiv9-0eVz>fTgpjI7oRyU&sjaPr zP$)!tdOFO_&GYp2^*3LYfe zXvTt@o7)RP9v&W>PX>PWVy8V-8~b`SHMN;9h^A~I4Iy!0V4$S5w3L-ZeTdy@uO0Hq z#EGDl6UY4jA)mg}{tKtKaq!?lv7(})kl_k6jayNpW*ad*{>Pp6tF3=KzT9HgEQSk2 zM@O56hlk6ns;XEyG@bvfo%V|_F{2C@2n`K2rkM-$$r&UHG&?>0k2~!Rf7>zKaN{xAA3_i}QlJqcQ>sj2y@ zk&)3$B&;hVBLnn33`$B$UC(#gFEsp>h~H^XXlrY$(@d4QXV0F@y?ghn^E>T%czAet zcz9+i%J&qvS_}1atpvt{W`#!2EQ<7tRnm5U#qco9$?PNH+3gXi=_yC5Scrs%Dg>7c z;3o*eftX#%43|;4w}0zBaUk*sGUPeE#kkQ_1)s`#7^YRiyX=lUw#pTsyMMNq;VM@I z=L-s-Bp^hRj=YWv*c8>`SaLqjRC(cavjKvIPEZfpU&e41$1>v~mqrKJP!-*56+%5J z4`vOC_*|re9m1o~PdJU^0f&YeuHsN)G;CdA0NeTm>MT0QaG5P$8b_giVgjsR{jA9Gm zUSxr5S?2hg-qL#v55xTZ(z3k=J@=J-<+yKXkMq8veJbULAr!G5RhuyZ*A*GdbEEyh#IkEChea~HQ$bApK2_K4^;v2Bu@fyzl>s9&A z4`*LyxQ^G(*KnQ0*pU?Los)S3R$tD6-i9}EWaVp8hU-}W>lNJbGC|vII~4ex#hLYU zaBlNlXnnB=8f)Je3pP;T_Vs|i$_si&I_n_K^+NkyCzMBBLdz{%NLqx5IQgEmB2bgt z8y8%a{+1&t3P+b98pCA~lt}hxi7*1!jx853T;`8E!~SRSI?dn+?NxXt@WY7cCY#~X zFvMM0h2{S`89V0--n}D_weRM zf1TMIz5kct60^5Q&HDY8DAfgD=AgQ57@=*zIT3KG5KzwoR8jzg=s2A7Vy~va0gH_z z+w}i&kvsRaDhBM{yw48N1rzWt2ek9$IGHKO@l-jK6Xa0y{lTj#xN|JL#AL}tSCRqT z1CpS68&FRH)Dq=58Y9P{FnYAv!R@Ol%1xKbC28h3sBs;ZcZZ-9100T&<3O++ihgqb zYKqEWZ9}2q;&C;#E90wGGoeO9>)5n>c4iSce>Fu*TUc!UZ@vIk9KSxO{FNgM))?1_lORL@JfC>F@7HR8$mq1<`3kR!*W|h|SI7A;c68 z;YPtAB60`O*Vl*O;9%|wqT3cg6~o@PAQp>3M>X;2(IY&3_z*2EEvz74UtjJDqSJ=# zP>Ly_s>vTjI8{x^gF#%+8ifD70Z1eg&{j$> zHC9$uxOM9mdsm0D^%icg2c6B)c=g=<6L2oJhS>uH_O=(gzyztOsi5%dL1(hm^|jK~ z^b_lxHxWoX{9?m(AOtL%Of#b9=4*Tr4`MSX?}zz zN@LSQn)+MNFqwvC^z=wTx5jWs5ZwjDicvTh@v~A}*9d=qf9?pPAw^Zg3c@MpTs)1Ex4f~p!ng)1!dU8h)UCjj=PVAf4bjgF()qEHQl)xpf z23K#^!8WQI=3!O17+TG~rnMHva*I- z-|FdRJ9c)WH~*q~&Ugx-OM=e&l;XT^IWG8DLN7qXzNWmQ26TNOIJFMc)co*g&tjtL zdKefOa7Pe*@Pk;~hN|isR8&@@rnUju**OphD^M&bN7~&C1Sk98yq7Y&9;9Nk4a$0Z zxFaYsGSZPg_z4060tEC2LIK^I*i5sOA6@%BgP zhhaSTz2?|ab_dd2PmGrZVKC1VZKWy5v{=!?@G#8Y9baoyhn|xUl*d3;6Ne#DJUX&m zVXnMBmf<2A8y2g1sBM(G9^C*>)lK844lEbA>T8X4#GZ#Z;W9cBO_Ae$8Xtc1LCR{q zWxr#%gv#2#Z1PasEOSxXfawWv)`6RkC2ZYIamhgs300x6$~4FFi+>IL$NBfUbLz+W zz(+aPPVGjVnL4V&O%Qf|KU|M)g7=BdxL#%guM&MY3D3etcn;CE{?Lur#+p;BJsGax ze&D_6hRle6Yse8s8{k}G09&CREQ)nuT&xMhLQSZ7s-oF*7dYRLZ7ue}z0?qw5>GoB_>bok0f?6v#?kA1)7j-Z!TUNv+#)c&`IylnpkX!v~ngZqPy>Myy)aDiECBaiPr zFC5J31g_kOg=s(p^jrhsS2O}W_ckb5ZJFTgdeFih-uw6KBo8Il$Y5Cwd{YD{X8~U) z0|z1i*IaOWJ*dKR)r26%4d-umK;;gg5-*2RgdF<=Wl-<}6x?K3cjjI0t_QVT{rqu^ z)ma?6SOASQKFna}dQe@+i8U$adyPKX9~!?(w-_5PR!}Oj z!K56Y8wv5=wx|}j3&+ir^_L{_>p?s`zvO?P{w5`iGD^WuB#rVHE(%JB%C5)wgbY7L za$(D)qz1}wxFqKl%7jcVNB^)KPX^?8ES00JPmV_tIhuRqXy}rmkpjXvFn?0g$6ONh z&S{SPSv?hYXb@r>t#K&;&r31{X{UTEodEO_DGTwwlVsGFII8 zn)J(Do#rPfpuqo56`qrj8x37g<*D;x8=kV~#kdYhYV42b18QcUf9ycbb~+vxECNaez^!B; zF%gK1qvyqt73X2%0ZAz?1g>8P{QUuMZy+of6Gi!`KX|a6ndywi#<_IPE?K>L9>WE?t*t+NVrEu6aMckqn#vIu4j35%#zugFK9Vk}@asX34GdlrpFL~w zNK$K)i@asW8K*jp?iZoN{@asYRdeG0~{cBCHXxwN`y_nu9@yzR%x!ms> zy_DL~cP!*#@dCZFcNrdrdC#$K+r|8zy1e#bTvGJF{)gAA+@v0r&!W6UqkTmJe9&_ z?%CblU%5AA%{x`L={__DEyEQ)vaIq+su`FFD3tWO zWOa3VXLt8z)eYe2wI;z^JF?#SyIK9s-2tugjgC!CYT<2oTdCCItpmcDzsrmo@UN)> z9}N{q26uTjX3X86d+6;w4;25nEBlN`dS~x(-=@mBs=|{;Lfi4rD@~PDHCqq*i!uLj zsUyP$r~!Yd6(g>2&FboUdvDGPW{Nr6GyZzayRrP_wdRLg+*u~8>s+zrr zd>&!3S{XO`k4eEN(r1s_xy8!VrJhn!Zxep|&vmeh-vfo1qrkhPMt1P1)b= zE8cm)qXi$RmnAb?hlbM;rR!p-2e;uH@9Q}2t^)SFnAJ9{(>`DO#*UoX+?AtL`tjOJ zb(K3Dn}!vPFBSjLdeF)f%K{j#vQ(=gMm3-nZz&d99JSs&^26&ve|pAn4Qj$G=j<1p z-+HC7f7_MDq1n3%6#4a_X>7G^8c=X*f`UUMzO-q?M;hgMKej*UQ>_L31}c7k5WgPu zf9;)VP*m9&hVPWgq~>4pBbG@e6O*W=;<(JX4NC@bSQH!vQ-Y=FfP%uXG=il9A|Ma} zMF{X^VVo(Tq>2Bnm^ors^03NtNRB} zoqN91U)_6-k)MUro{K&S#lp$Z3q1(Qp=5UD}Z+J;}^$ZuFk%OO-T}SVL;Q}Gq-S{c&JW|OGLBUy_ z0HM)MK}}IAX8}}RcJ6SS7B75#B-4XL!)x^K9>Q6TTBH<1kr>HLaWz9J2l0M zQfhTjs98~%Q0gF_R6`^qQBoB~#*_fDJ@P6y1W~mDh$#}Jqi87_5~U(h3JpmjQF0O` zQ|KU(YcVEM1BC0LGHwW>QVv#-!N3Z7B}zk4PqCNWYf-WvM1k0Hu!I|eg3dg8CFpf@ z|23M_6pc=YvbsxjT7nzc`-9@w!zp+VHw5v%YXmEZqNlu;L?J<1b9K}LQtwua^FkPeR8Evdvg;RuCO^c|3y!4uR$i4 zLq4g3j50xyuu(%SS7ThJhKP*{BjO3H56F`+T;b)&nUKlh8kvfH={X3?$VSNde1v3H z;9yn-4rNy$BIgF8a&N*jA`zn#Dp>pFOBt?UPL~ml97JZ9qo7BIqFx0Gg)$WODpA;{ zLUF$uB?DTNKAl2r{w<71I4FoNDfXW)8#bV9NdLaDpLs4(lAt_p36hYYD1JGLgbEbB z967>C8ch?(?~>yZCBIXK%Uu#g^GoqeJjp#lVz~8H82P9|Q)6-MyTQRKMdvi?be@&$HDix?yH!_ITyWl~v1@aqxsX zlG8P{q!KtKRtky{+&n}*n$&-o3M20(iRgFlgN4V=Cv|h!`az+(8A@E6b)>A)-8f*X1 zb8bIrdwcsX8ylNUs#MKyR(VOgc*J3)_p<-)y^V zw~R4D5S`^;%_KR|?Ire-QNbBJZ;XZDlC``w@5^IPE?E7M;VNBM9e+)7JUdDqx`&&S zF{OS6gYqe4!a}HqN-%II5RbCI=x@oi@NGVC_7S&6%~eO6&a$uW_17MPO8f|b5->=i z8*e~Y!Pii=ABAQh53;rtbeHVF!?T~)HJw_yfh%&(b(U=~jCb;(5Ix3t?=AG!lZdJ~ zv|S5;@VYk??MYBHheO?c7TTwI7<&*6L5>p|Qa;V$!ep#7s0W}CHDUa5GDMBh7{1Lz zcd;|-PMSeG_7G5X0;AVq=t}_v`+*@obUo3iPhO&9raLqlOZDnOK;8=NP!T4Y{{=-$ zIOPC_iXGVZ@S+?TI1NMF0Vpah@uJKE6M`hvC4M4hraNs#>!f-m37YI8NdiDr36&rY zih6InD0P6YD*@;^hJ87!;dyQuMsC|-#)jh{dL1=0G36$HA(Agu?+ZvmrM zz~fthm%SR^BxYy&5*#etguI4-V{g7AY!hceR1<}oBlD)epeMOj8iVdRpcDd|StW_v_n2Lg?ldPEBh9o52+>4C+@$ll?V0G+AsEV1RW~Muj`Ia|&D)&Pz=7X5>U5L2ij>BcUk#;W%uIJ4$Kl}p&Gu@$;@rSfB_N2vM zcmJI*Oo{IUvPNt@y#N;0JoA`eb1 zHK|Wp%&$AX$RKJ;#NbsP>W*9Dv#1&BZ!*^6FQH5$$@!4sI<-d^+SJ6&zeSa*x;kdg ohQEd}qK`vo;P=7IuNV&TA0W04<7$O;LI3~&07*qoM6N<$f{P)dQvd(} literal 0 HcmV?d00001 diff --git a/jappixmini/jappix/img/sprites/welcome.png b/jappixmini/jappix/img/sprites/welcome.png new file mode 100644 index 0000000000000000000000000000000000000000..9044bd9c8a3b325fb38841b406c3a01540ad1e0f GIT binary patch literal 4356 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 Yj+&U0O}1jCP0zkL>&zYuN5M<~1^P+;)&Kwi literal 0 HcmV?d00001 diff --git a/jappixmini/jappix/img/wait/wait-medium.png b/jappixmini/jappix/img/wait/wait-medium.png new file mode 100644 index 0000000000000000000000000000000000000000..4b9a780bd92b919df6391e5ba0048ff9425d5ed6 GIT binary patch literal 4129 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 literal 0 HcmV?d00001 diff --git a/jappixmini/jappix/img/wait/wait-small.gif b/jappixmini/jappix/img/wait/wait-small.gif new file mode 100644 index 0000000000000000000000000000000000000000..c4b5787e474c3ddf5cbae8f8cc49368642fd2fc2 GIT binary patch literal 847 zcmZ?wbhEHb6krfw_`<-jZQC|uW8+PmHu?Memz9;7nwq+~xh-6{u(Pw%*48#ZKR-P^ zeZ`6u4Gj%5X3Pi=508tBV;}`6{^#~{4GDI33~)8lGhk)}>Q($F+Z`3#dv5WDCfV46FeP3VkV=^OhQj* zDbt1zY&Sa^qI+3vm@-=fCJMANcD63+TcfGd;2HH}kxJ5sT^^GrShTD#RgfP#Y{ zm(PKQ56OY<%}h>Po^A^;ouOc}$}}`3aGUlErG1Ojrx>gdTeoOozhkz*d1XfiZkqse z19lrumIed9NHz;CUe0!9MQoNQ0DY+Zd4k3*5f3e{BLO0ux=Y#(-y1h_Svj9J^xopp z()5tcLvM!4&5e}|90L0;o?*R_#jV#E?&5HVFOp%Afd{8ptBygR(-p41Yyq!lZ+SxP zP0N{CYE-g$V>J1By0Kas1PRs#g(*yiB1auoG&{?&tr0(#-Q6^YQPJs1f;~gycBXw3 zlwF%Fs+|Qd+3+^3dXh#)LNBP`720MRz3mmKMC${G9f`0jo72zX4-! zg#(YPp}?V&61lMw$9GD2^jv7Wp}{WD#bUyw@LWSjVwsy_q6b4n@C=T|T+=068IxQ* zwy-g*+L@T7!_}wSP+%;Ud_;MJ`VZbWO6(?P<^gJ25&8`pJ-sSeEscj->H+i^$1(>K zhC`c' + _e("Commands") + '' + + + '
' + + '
' + + + '
' + + '
' + + + '
'; + + // Create the popup + createPopup('adhoc', html); + + // Associate the events + launchAdHoc(); + + return false; +} + +// Quits the adhoc popup +function closeAdHoc() { + // Destroy the popup + destroyPopup('adhoc'); + + return false; +} + +// Retrieves an entity adhoc command +function retrieveAdHoc(xid) { + // Open the popup + openAdHoc(); + + // Add a XID marker + $('#adhoc .adhoc-head').html('' + getBuddyName(xid).htmlEnc() + ' (' + xid.htmlEnc() + ')'); + + // Get the highest entity resource + var highest = getHighestResource(xid); + + if(highest) + xid = highest; + + // Start a new adhoc command + dataForm(xid, 'command', '', '', 'adhoc'); + + return false; +} + +// Starts an adhoc command on the user server +function serverAdHoc(server) { + // Open the popup + openAdHoc(); + + // Add a XID marker + $('#adhoc .adhoc-head').html('' + server.htmlEnc() + ''); + + // Start a new adhoc command + dataForm(server, 'command', '', '', 'adhoc'); +} + +// Plugin launcher +function launchAdHoc() { + // Click event + $('#adhoc .bottom .finish').click(closeAdHoc); +} diff --git a/jappixmini/jappix/js/anonymous.js b/jappixmini/jappix/js/anonymous.js new file mode 100644 index 00000000..88eaa7dc --- /dev/null +++ b/jappixmini/jappix/js/anonymous.js @@ -0,0 +1,131 @@ +/* + +Jappix - An open social platform +These are the anonymous mode JS script for Jappix + +------------------------------------------------- + +License: AGPL +Authors: Vanaryon, LinkMauve +Last revision: 02/10/11 + +*/ + +// Connected to an anonymous session +function anonymousConnected(con) { + logThis('Jappix (anonymous) is now connected.', 3); + + // Connected marker + CONNECTED = true; + CURRENT_SESSION = true; + RECONNECT_TRY = 0; + RECONNECT_TIMER = 0; + + // Not resumed? + if(!RESUME) { + // Create the app + createTalkPage(); + + // Send our first presence + firstPresence(''); + + // Set last activity stamp + LAST_ACTIVITY = getTimeStamp(); + + // Create the new groupchat + checkChatCreate(generateXID(ANONYMOUS_ROOM, 'groupchat'), 'groupchat'); + + // Remove some nasty elements for the anonymous mode + $('.tools-mucadmin, .tools-add').remove(); + } + + // Resumed + else { + // Send again our presence + presenceSend(); + + // Change the title + updateTitle(); + } + + // Remove the waiting icon + removeGeneralWait(); +} + +// Disconnected from an anonymous session +function anonymousDisconnected() { + logThis('Jappix (anonymous) is now disconnected.', 3); +} + +// Logins to a anonymous account +function anonymousLogin(server) { + try { + // We define the http binding parameters + oArgs = new Object(); + + if(HOST_BOSH_MAIN) + oArgs.httpbase = HOST_BOSH_MAIN; + else + oArgs.httpbase = HOST_BOSH; + + // We create the new http-binding connection + con = new JSJaCHttpBindingConnection(oArgs); + + // And we handle everything that happen + con.registerHandler('message', handleMessage); + con.registerHandler('presence', handlePresence); + con.registerHandler('iq', handleIQ); + con.registerHandler('onconnect', anonymousConnected); + con.registerHandler('onerror', handleError); + con.registerHandler('ondisconnect', anonymousDisconnected); + + // We set the anonymous connection parameters + oArgs = new Object(); + oArgs.domain = server; + oArgs.authtype = 'saslanon'; + oArgs.resource = JAPPIX_RESOURCE + ' Anonymous (' + (new Date()).getTime() + ')'; + oArgs.secure = true; + oArgs.xmllang = XML_LANG; + + // We connect ! + con.connect(oArgs); + + // Change the page title + pageTitle('wait'); + } + + catch(e) { + // Logs errors + logThis('Error while anonymous loggin in: ' + e, 1); + + // Reset Jappix + anonymousDisconnected(); + + // Open an unknown error + openThisError(2); + } + + finally { + return false; + } +} + +// Plugin launcher +function launchAnonymous() { + logThis('Anonymous mode detected, connecting...', 3); + + // We add the login wait div + showGeneralWait(); + + // Get the vars + if(LINK_VARS['r']) + ANONYMOUS_ROOM = LINK_VARS['r']; + if(LINK_VARS['n']) + ANONYMOUS_NICK = LINK_VARS['n']; + + // Fire the login action + anonymousLogin(HOST_ANONYMOUS); +} + +// Launch this plugin! +$(document).ready(launchAnonymous); diff --git a/jappixmini/jappix/js/archives.js b/jappixmini/jappix/js/archives.js new file mode 100644 index 00000000..387a379f --- /dev/null +++ b/jappixmini/jappix/js/archives.js @@ -0,0 +1,418 @@ +/* + +Jappix - An open social platform +These are the archives functions for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 03/03/11 + +*/ + +// Opens the archive tools +function openArchives() { + // Popup HTML content + var html = + '
' + _e("Message archives") + '
' + + + '
' + + '
' + + '' + + + '
' + + '
' + + + '
' + + '' + + '' + _e("Please select a friend to view the chat history.") + '' + + '
' + + + '
' + + '
' + + + '
' + + '
' + + + '' + _e("Close") + '' + + '
'; + + // Create the popup + createPopup('archives', html); + + // Associate the events + launchArchives(); + + // Get all the buddies in our roster + var buddies = getAllBuddies(); + var options = ''; + + for(i in buddies) { + var current = buddies[i]; + + // Add the current buddy + options += ''; + } + + // Can append the buddy HTML code? + if(options) + $('#archives .filter .friend').append(options); + + return false; +} + +// Closes the archive tools +function closeArchives() { + // Destroy the popup + destroyPopup('archives'); + + return false; +} + +// Gets the archives list for a buddy +function getListArchives(xid) { + // Reset the archives viewer + $('#archives .logs').empty(); + + // Show the waiting icon + $('#archives .wait').show(); + + // Apply the ID + var id = genID(); + $('#archives').attr('data-session', id); + + // New IQ + var iq = new JSJaCIQ(); + iq.setType('get'); + iq.setID(id); + + var list = iq.appendNode('list', {'xmlns': NS_URN_ARCHIVE, 'with': xid}); + var set = list.appendChild(iq.buildNode('set', {'xmlns': NS_RSM})); + set.appendChild(iq.buildNode('max', {'xmlns': NS_RSM}, '0')); + + con.send(iq, handleListArchives); + + logThis('Getting archives list for: ' + xid + '...'); +} + +// Handles the archives list for a buddy +function handleListArchives(iq) { + // Hide the waiting icon + $('#archives .wait').hide(); + + // Any error? + if(handleErrorReply(iq) || !exists('#archives[data-session=' + iq.getID() + ']')) + return; + + // Get the last archive date + var last = $(iq.getNode()).find('list set changed').text(); + + // Any last archive? + if(last) { + // Read the date + var date = Date.jab2date(last); + + // Change the datepicker value + $('#archives .filter .date').DatePickerSetDate(date, true); + + // Retrieve the archives + checkChangeArchives(); + } + + logThis('Got archives list.', 2); +} + +// Gets the archives for a day +function getDayArchives(xid, date) { + // Reset the archives viewer + $('#archives .logs').empty(); + + // Show the waiting icon + $('#archives .wait').show(); + + // Apply the ID + var id = genID(); + $('#archives').attr('data-session', id); + + // New IQ + var iq = new JSJaCIQ(); + iq.setType('get'); + iq.setID(id); + + iq.appendNode('list', {'xmlns': NS_URN_ARCHIVE, 'with': xid, 'start': date + 'T00:00:00Z', 'end': date + 'T23:59:59Z'}); + + con.send(iq, handleDayArchives); + + logThis('Getting day archives (' + date + ') for: ' + xid + '...'); +} + +// Handles the archives for a day +function handleDayArchives(iq) { + // Hide the waiting icon + $('#archives .wait').hide(); + + // Any error? + if(handleErrorReply(iq) || !exists('#archives[data-session=' + iq.getID() + ']')) + return; + + // Get each archive thread + $(iq.getNode()).find('chat').each(function() { + // Current values + var xid = $(this).attr('with'); + var start = $(this).attr('start'); + + if(xid && start) + $('#archives .logs').append(''); + }); + + // Display the day + var date = parseDay($('#archives .filter .date').DatePickerGetDate(true) + 'T00:00:00Z' + getDateTZO()); + + // Try to get the first thread + var pending = '#archives input.archives-pending:first'; + + if(!exists(pending)) + date = printf(_e("Nothing found for: %s"), date); + + else { + retrieveArchives($(pending).attr('data-with'), $(pending).attr('data-start')); + $(pending).remove(); + } + + $('#archives .current .time').text(date); + + logThis('Got day archives.', 2); +} + +// Retrieves a specified archive collection +function retrieveArchives(xid, start) { + // Show the waiting icon + $('#archives .wait').show(); + + // Apply the ID + var id = genID(); + $('#archives').attr('data-session', id); + + // New IQ + var iq = new JSJaCIQ(); + iq.setType('get'); + iq.setID(id); + + var list = iq.appendNode('retrieve', {'xmlns': NS_URN_ARCHIVE, 'with': xid, 'start': start}); + + con.send(iq, handleRetrieveArchives); + + logThis('Retrieving archives (start: ' + start + ') for: ' + xid + '...'); +} + +// Handles a specified archive collection +function handleRetrieveArchives(iq) { + // Hide the waiting icon + $('#archives .wait').hide(); + + // Any error? + if(handleErrorReply(iq) || !exists('#archives[data-session=' + iq.getID() + ']')) + return; + + // Get the node + var chat = $(iq.getNode()).find('chat:first'); + + // Get the buddy XID + var xid = bareXID(chat.attr('with')); + + // Get the start date & stamp + var start_date = Date.jab2date(chat.attr('start')); + var start_stamp = extractStamp(start_date); + + // Parse the result chat + chat.find('to, from').each(function() { + var node = (this).nodeName; + var stamp = start_stamp + parseInt($(this).attr('secs')); + var date = extractTime(new Date(stamp * 1000)); + var body = $(this).find('body').text(); + + // Is it my message? + if((node == 'to') && body) + displayMessage('chat', getXID(), 'archives', getBuddyName(getXID()).htmlEnc(), body, date, start_stamp, 'user-message', true, '', 'me'); + + // Is it a buddy message? + else if((node == 'from') && body) + displayMessage('chat', xid, 'archives', getBuddyName(xid).htmlEnc(), body, date, start_stamp, 'user-message', true, '', 'him'); + }); + + // Not the latest thread? + var pending = '#archives input.archives-pending:first'; + + if(exists(pending)) { + retrieveArchives($(pending).attr('data-with'), $(pending).attr('data-start')); + $(pending).remove(); + } + + // Everything has been retrieved, get the avatars + else { + getAvatar(getXID(), 'cache', 'true', 'forget'); + getAvatar(xid, 'cache', 'true', 'forget'); + } + + logThis('Got archives.', 2); +} + +// Gets the archiving configuration +function getConfigArchives() { + // Lock the archiving options + $('#archiving').attr('checked', false).attr('disabled', true); + + // Get the archiving configuration + var iq = new JSJaCIQ(); + iq.setType('get'); + + iq.appendNode('pref', {'xmlns': NS_URN_ARCHIVE}); + + con.send(iq, handleGetConfigArchives); +} + +// Handles the archiving configuration +function handleGetConfigArchives(iq) { + // Reset the options stuffs + waitOptions('archives'); + + // Unlock the archiving options + $('#archiving').removeAttr('disabled'); + + // End if not a result + if(!iq || (iq.getType() != 'result')) + return; + + // Extract the preferences from the IQ + var enabled = $(iq.getNode()).find('pref auto').attr('save'); + + // Define the input enabling/disabling vars + var checked = true; + + if(enabled != 'true') + checked = false; + + // Apply the values + $('#archiving').attr('checked', checked); +} + +// Configures the archiving on the server +function configArchives(enabled) { + // Configure the auto element + var iq = new JSJaCIQ(); + iq.setType('set'); + + iq.appendNode('auto', {'xmlns': NS_URN_ARCHIVE, 'save': enabled}); + + con.send(iq, handleConfigArchives); + + // Configure the default element + var iq = new JSJaCIQ(); + iq.setType('set'); + + var pref = iq.appendNode('pref', {'xmlns': NS_URN_ARCHIVE}); + pref.appendChild(iq.appendNode('default', {'xmlns': NS_URN_ARCHIVE, 'otr': 'concede', 'save': 'body'})); + + con.send(iq); + + // Configure the method element + var iq = new JSJaCIQ(); + iq.setType('set'); + + var mType = new Array('auto', 'local', 'manual'); + var mUse = new Array('prefer', 'concede', 'concede'); + + var pref = iq.appendNode('pref', {'xmlns': NS_URN_ARCHIVE}); + + for(i in mType) + pref.appendChild(iq.appendNode('method', {'xmlns': NS_URN_ARCHIVE, 'type': mType[i], 'use': mUse[i]})); + + con.send(iq); + + // Logger + logThis('Configuring archives...', 3); +} + +// Handles the archives configuration +function handleConfigArchives(iq) { + if(!iq || (iq.getType() != 'result')) + logThis('Archives not configured.', 2); + else + logThis('Archives configured.', 3); +} + +// Checks if the datepicker has changed +function checkChangeArchives() { + var xid = $('#archives .filter .friend').val(); + var date = $('#archives .filter .date').DatePickerGetDate(true); + + // No XID? + if(!xid || !xid.length) + return; + + // Too many value? + if(xid.length > 1) { + $('#archives .filter .friend').val(xid[0]); + + return; + } + + // Get the first XID + xid = xid[0]; + + // Get the archives + getDayArchives(xid, date); +} + +// Update the archives with the selected XID +function updateArchives() { + // Read the values + var xid = $('#archives .filter .friend').val(); + var date = $('#archives .filter .date').DatePickerGetDate(true); + + // No XID? + if(!xid || !xid.length) + return; + + // Too many value? + if(xid.length > 1) { + $('#archives .filter .friend').val(xid[0]); + + return; + } + + // Get the first XID + xid = xid[0]; + + // Apply the current marker + $('#archives .current .name').text(getBuddyName(xid)); + $('#archives .current .time').text(parseDay(date + 'T00:00:00Z' + getDateTZO())); + + // Get the archives + getListArchives(xid, date); +} + +// Plugin launcher +function launchArchives() { + // Current date + var current_date = explodeThis('T', getXMPPTime(), 0); + + // Datepicker + $('#archives .filter .date').DatePicker({ + flat: true, + date: current_date, + current: current_date, + calendars: 1, + starts: 1, + onChange: checkChangeArchives + }); + + // Click events + $('#archives .bottom .finish').click(function() { + return closeArchives(); + }); + + // Change event + $('#archives .filter .friend').change(updateArchives); +} diff --git a/jappixmini/jappix/js/audio.js b/jappixmini/jappix/js/audio.js new file mode 100644 index 00000000..f83583a9 --- /dev/null +++ b/jappixmini/jappix/js/audio.js @@ -0,0 +1,46 @@ +/* + +Jappix - An open social platform +These are the audio JS scripts for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 10/08/11 + +*/ + +// Plays the given sound ID +function soundPlay(num) { + try { + // Not supported! + if((BrowserDetect.browser == 'Explorer') && (BrowserDetect.version < 9)) + return false; + + // If the sounds are enabled + if(getDB('options', 'sounds') == '1') { + // If the audio elements aren't yet in the DOM + if(!exists('#audio')) { + $('body').append( + '
' + + '
' + ); + } + + // We play the target sound + var playThis = document.getElementById('audio').getElementsByTagName('audio')[num]; + playThis.load(); + playThis.play(); + } + } + + catch(e) {} + + finally { + return false; + } +} diff --git a/jappixmini/jappix/js/autocompletion.js b/jappixmini/jappix/js/autocompletion.js new file mode 100644 index 00000000..52d3c47e --- /dev/null +++ b/jappixmini/jappix/js/autocompletion.js @@ -0,0 +1,99 @@ +/* + +Jappix - An open social platform +These are the autocompletion tools JS script for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 12/11/10 + +*/ + +// Sort an array with insensitivity to the case +function caseInsensitiveSort(a, b) { + // Put the two strings into lower case + a = a.toLowerCase(); + b = b.toLowerCase(); + + // Process the sort + if(a > b) + return 1; + if(a < b) + return -1; +} + +// Creates an array with the autocompletion results +function processAutocompletion(query, id) { + // Replace forbidden characters in regex + query = escapeRegex(query); + + // Create an empty array + var results = new Array(); + + // Search in the roster + $('#' + id + ' .user').each(function() { + var nick = $(this).find('.name').text(); + var regex = new RegExp('(^)' + query, 'gi'); + + if(nick.match(regex)) + results.push(nick); + }); + + // Sort the array + results = results.sort(caseInsensitiveSort); + + // Return the results array + return results; +} + +// Resets the autocompletion tools +function resetAutocompletion(hash) { + $('#' + hash + ' .message-area').removeAttr('data-autocompletion-pointer').removeAttr('data-autocompletion-query'); +} + +// Autocompletes the chat input nick +function createAutocompletion(hash) { + // Initialize + var vSelector = $('#' + hash + ' .message-area'); + var value = vSelector.val(); + if(!value) + resetAutocompletion(hash); + var query = vSelector.attr('data-autocompletion-query'); + + // The autocompletion has not been yet launched + if(query == undefined) { + query = value; + vSelector.attr('data-autocompletion-query', query); + } + + // Get the pointer + var pointer = vSelector.attr('data-autocompletion-pointer'); + var i = 0; + + if(pointer) + i = parseInt(pointer); + + // We get the nickname + var nick = processAutocompletion(query, hash)[i]; + + // Shit, this is my nick! + if((nick != undefined) && (nick.toLowerCase() == getMUCNick(hash).toLowerCase())) { + // Increment + i++; + + // Get the next nick + nick = processAutocompletion(query, hash)[i]; + } + + // We quote the nick + if(nick != undefined) { + // Increment + i++; + quoteMyNick(hash, nick); + + // Put a pointer + vSelector.attr('data-autocompletion-pointer', i); + } +} diff --git a/jappixmini/jappix/js/avatar.js b/jappixmini/jappix/js/avatar.js new file mode 100644 index 00000000..4234ffc7 --- /dev/null +++ b/jappixmini/jappix/js/avatar.js @@ -0,0 +1,205 @@ +/* + +Jappix - An open social platform +These are the avatar JS scripts for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 01/03/11 + +*/ + +// Requests the avatar of a given user +var AVATAR_PENDING = []; + +function getAvatar(xid, mode, enabled, photo) { + /* REF: http://xmpp.org/extensions/xep-0153.html */ + + // No need to get the avatar, another process is yet running + if(existArrayValue(AVATAR_PENDING, xid)) + return false; + + // Initialize: XML data is in one SQL entry, because some browser are sloooow with SQL requests + var xml = XMLFromString(getPersistent('avatar', xid)); + var forced = false; + + // Retrieving forced? + if($(xml).find('forced').text() == 'true') + forced = true; + + // No avatar in presence + if(!photo && !forced && (enabled == 'true')) { + // Pending marker + AVATAR_PENDING.push(xid); + + // Reset the avatar + resetAvatar(xid, hex_md5(xid)); + + logThis('No avatar for: ' + xid, 2); + } + + // Try to catch the avatar + else { + // Define some stuffs + var type = $(xml).find('type').text(); + var binval = $(xml).find('binval').text(); + var checksum = $(xml).find('checksum').text(); + var updated = false; + + // Process the checksum of the avatar + if((checksum == photo) || (photo == 'forget') || forced) + updated = true; + + // If the avatar is yet stored and a new retrieving is not needed + if((mode == 'cache') && type && binval && checksum && updated) { + // Pending marker + AVATAR_PENDING.push(xid); + + // Display the cache avatar + displayAvatar(xid, hex_md5(xid), type, binval); + + logThis('Read avatar from cache: ' + xid, 3); + } + + // Else if the request has not yet been fired, we get it + else if((!updated || (mode == 'cache' && !updated) || (mode == 'force') || (photo = 'forget')) && (enabled != 'false')) { + // Pending marker + AVATAR_PENDING.push(xid); + + // Get the latest avatar + var iq = new JSJaCIQ(); + iq.setType('get'); + iq.setTo(xid); + + iq.appendNode('vCard', {'xmlns': NS_VCARD}); + + con.send(iq, handleAvatar); + + logThis('Get avatar from server: ' + xid, 3); + } + } + + return true; +} + +// Handles the avatar +function handleAvatar(iq) { + // Extract the XML values + var handleXML = iq.getNode(); + var handleFrom = fullXID(getStanzaFrom(iq)); + + // Is this me? Remove the resource! + if(bareXID(handleFrom) == getXID()) + handleFrom = bareXID(handleFrom); + + // Get some other values + var hash = hex_md5(handleFrom); + var find = $(handleXML).find('vCard'); + var aChecksum = 'none'; + var oChecksum = null; + + // Read our own checksum + if(handleFrom == getXID()) { + oChecksum = getDB('checksum', 1); + + // Avoid the "null" value + if(!oChecksum) + oChecksum = ''; + } + + // vCard not empty? + if(find.size()) { + // We get our profile details + if(handleFrom == getXID()) { + // Get the names + var names = generateBuddyName(iq); + + // Write the values to the database + setDB('profile', 'name', names[0]); + setDB('profile', 'nick', names[1]); + } + + // We get the avatar + var aType = find.find('TYPE:first').text(); + var aBinval = find.find('BINVAL:first').text(); + + // No binval? + if(!aBinval) { + aType = 'none'; + aBinval = 'none'; + } + + // Enough data + else { + // No type? + if(!aType) + aType = 'image/png'; + + // Process the checksum + else + aChecksum = hex_sha1(Base64.decode(aBinval)); + } + + // We display the user avatar + displayAvatar(handleFrom, hash, aType, aBinval); + + // Store the avatar + setPersistent('avatar', handleFrom, '' + aType + '' + aBinval + '' + aChecksum + 'false'); + + logThis('Avatar retrieved from server: ' + handleFrom, 3); + } + + // vCard is empty + else + resetAvatar(handleFrom); + + // We got a new checksum for us? + if(((oChecksum != null) && (oChecksum != aChecksum)) || !FIRST_PRESENCE_SENT) { + // Define a proper checksum + var pChecksum = aChecksum; + + if(pChecksum == 'none') + pChecksum = ''; + + // Update our temp. checksum + setDB('checksum', 1, pChecksum); + + // Send the stanza + if(FIRST_PRESENCE_SENT) + presenceSend(pChecksum); + else + getStorage(NS_OPTIONS); + } +} + +// Reset the avatar of an user +function resetAvatar(xid, hash) { + // Store the empty avatar + setPersistent('avatar', xid, 'nonenonenonefalse'); + + // Display the empty avatar + displayAvatar(xid, hash, 'none', 'none'); +} + +// Displays the avatar of an user +function displayAvatar(xid, hash, type, binval) { + // Initialize the vars + var container = hash + ' .avatar-container'; + var code = ''; + + // Replace with the new avatar (in the roster and in the chat) + $('.' + container).html(code); + + // We can remove the pending marker + removeArrayValue(AVATAR_PENDING, xid); +} diff --git a/jappixmini/jappix/js/base64.js b/jappixmini/jappix/js/base64.js new file mode 100644 index 00000000..1cf2dc74 --- /dev/null +++ b/jappixmini/jappix/js/base64.js @@ -0,0 +1,80 @@ +// This code was written by Tyler Akins and has been placed in the +// public domain. It would be nice if you left this header intact. +// Base64 code from Tyler Akins -- http://rumkin.com + +var Base64 = (function () { + var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + + var obj = { + /** + * Encodes a string in base64 + * @param {String} input The string to encode in base64. + */ + encode: function (input) { + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + + do { + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + + keyStr.charAt(enc3) + keyStr.charAt(enc4); + } while (i < input.length); + + return output; + }, + + /** + * Decodes a base64 string. + * @param {String} input The string to decode. + */ + decode: function (input) { + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + + // remove all characters that are not A-Z, a-z, 0-9, +, /, or = + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + do { + enc1 = keyStr.indexOf(input.charAt(i++)); + enc2 = keyStr.indexOf(input.charAt(i++)); + enc3 = keyStr.indexOf(input.charAt(i++)); + enc4 = keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output = output + String.fromCharCode(chr1); + + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + } while (i < input.length); + + return output; + } + }; + + return obj; +})(); diff --git a/jappixmini/jappix/js/board.js b/jappixmini/jappix/js/board.js new file mode 100644 index 00000000..1d26acee --- /dev/null +++ b/jappixmini/jappix/js/board.js @@ -0,0 +1,141 @@ +/* + +Jappix - An open social platform +These are the notification board JS script for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 12/03/11 + +*/ + +// Creates a board panel +function createBoard(type, id) { + // Text var + var text = ''; + + // Info + if(type == 'info') { + switch(id) { + // Password change + case 1: + text = _e("Your password has been changed, now you can connect to your account with your new login data."); + + break; + + // Account deletion + case 2: + text = _e("Your XMPP account has been removed, bye!"); + + break; + + // Account logout + case 3: + text = _e("You have been logged out of your XMPP account, have a nice day!"); + + break; + + // Groupchat join + case 4: + text = _e("The room you joined seems not to exist. You should create it!"); + + break; + + // Groupchat removal + case 5: + text = _e("The groupchat has been removed, now someone else will be able to recreate it."); + + break; + + // Non-existant groupchat user + case 6: + text = _e("The user that you want to reach is not present in the room."); + + break; + } + } + + // Error + else { + switch(id) { + // Custom error + case 1: + text = '' + _e("Error") + ' » '; + + break; + + // Network error + case 2: + text = _e("Jappix has been interrupted by a network issue, a bug or bad login (check that you entered the right credentials), sorry for the inconvenience."); + + break; + + // List retrieving error + case 3: + text = _e("The element list on this server could not be obtained!"); + + break; + + // Attaching error + case 4: + text = printf(_e("An error occured while uploading your file: maybe it is too big (%s maximum) or forbidden!"), JAPPIX_MAX_UPLOAD); + + break; + } + } + + // No text? + if(!text) + return false; + + // Append the content + $('#board').append('
' + text + '
'); + + // Events (click and auto-hide) + $('#board .one-board.' + type + '[data-id=' + id + ']') + + .click(function() { + closeThisBoard(this); + }) + + .oneTime('5s', function() { + closeThisBoard(this); + }) + + .slideDown(); + + return true; +} + +// Destroys the existing board notifications +function destroyBoard() { + $('#board').empty(); +} + +// Executes a given action on the notification board +function actionBoard(id, type) { + // In a first, we destroy other boards + destroyBoard(); + + // Then we display the board + createBoard(type, id); +} + +// Opens a given error ID +function openThisError(id) { + actionBoard(id, 'error'); +} + +// Opens a given info ID +function openThisInfo(id) { + actionBoard(id, 'info'); +} + +// Closes a given board +function closeThisBoard(board) { + $(board).slideUp('normal', function() { + $(this).remove(); + }); +} diff --git a/jappixmini/jappix/js/browser-detect.js b/jappixmini/jappix/js/browser-detect.js new file mode 100644 index 00000000..39edaf2b --- /dev/null +++ b/jappixmini/jappix/js/browser-detect.js @@ -0,0 +1,124 @@ +/* BROWSER DETECT + * http://www.quirksmode.org/js/detect.html + */ + +var BrowserDetect = { + init: function () { + this.browser = this.searchString(this.dataBrowser) || "An unknown browser"; + this.version = this.searchVersion(navigator.userAgent) + || this.searchVersion(navigator.appVersion) + || "an unknown version"; + this.OS = this.searchString(this.dataOS) || "an unknown OS"; + }, + + searchString: function (data) { + for (var i=0;i ' + _e("no subject defined for this room.") + '

'; + specialCode = '

' + _e("Moderators") + '

' + _e("Participants") + '

' + _e("Visitors") + '

' + _e("Others") + '

'; + specialLink = ''; + specialStyle = ''; + + // Is this a gateway? + if(xid.match(/%/)) + specialDisabled = ''; + else + specialDisabled = ' disabled=""'; + } + + // Chat (or other things?!) special code + else { + specialAttributes = ' data-type="chat"'; + specialAvatar = '
'; + specialName = '

'; + specialCode = '
'; + specialLink = ''; + specialStyle = ' style="display: none;"'; + specialDisabled = ''; + } + + // Not a groupchat private chat, we can use the buddy add icon + if((type == 'chat') || (type == 'groupchat')) { + var addTitle; + + if(type == 'chat') + addTitle = _e("Add this contact to your friends"); + else + addTitle = _e("Add this groupchat to your favorites"); + + specialLink += ''; + } + + // IE DOM parsing bug fix + var specialStylePicker = '
' + + '' + + '
'; + + if((BrowserDetect.browser == 'Explorer') && (BrowserDetect.version < 9)) + specialStylePicker = ''; + + // Append the chat HTML code + $('#page-engine').append( + '
' + + '
' + + specialAvatar + + + '
' + + '

' + nick.htmlEnc() + '

' + + specialName + + '
' + + '
' + + + specialCode + + + '
' + + '' + + + '
' + + '' + + '
' + + '
' + + '
' + ); + + // Click event: chat cleaner + $(path + 'tools-clear').click(function() { + cleanChat(id); + }); + + // Click event: user-infos + $(path + 'tools-infos').click(function() { + openUserInfos(xid); + }); +} + +// Generates the chat switch elements +function generateSwitch(type, id, xid, nick) { + // Path to the element + var chat_switch = '#page-switch .'; + + // Special code + var specialClass = ' unavailable'; + var show_close = true; + + // Groupchat + if(type == 'groupchat') { + specialClass = ' groupchat-default'; + + if(isAnonymous() && (xid == generateXID(ANONYMOUS_ROOM, 'groupchat'))) + show_close = false; + } + + // Generate the HTML code + var html = '
' + + '
' + + + '
' + nick.htmlEnc() + '
'; + + // Show the close button if not MUC and not anonymous + if(show_close) + html += '
x
'; + + // Close the HTML + html += '
'; + + // Append the HTML code + $(chat_switch + 'chans, ' + chat_switch + 'more-content').append(html); +} + +// Cleans given the chat lines +function cleanChat(chat) { + $('#page-engine #' + chat + ' .content .one-group').remove(); + + $(document).oneTime(10, function() { + $('#page-engine #' + chat + ' .text .message-area').focus(); + }); +} + +// Creates a new chat +function chatCreate(hash, xid, nick, type) { + logThis('New chat: ' + xid, 3); + + // Create the chat content + generateChat(type, hash, xid, nick); + + // Create the chat switcher + generateSwitch(type, hash, xid, nick); + + // If the user is not in our buddy-list + if(type == 'chat') { + // Add button + if(!exists('#buddy-list .buddy[data-xid=' + escape(xid) + ']')) + $('#' + hash + ' .tools-add').click(function() { + // Hide the icon (to tell the user all is okay) + $(this).hide(); + + // Send the subscribe request + addThisContact(xid, nick); + }).show(); + + // Archives button + else if(enabledArchives() || enabledArchives('auto') || enabledArchives('manual') || enabledArchives('manage')) + $('#' + hash + ' .tools-archives').click(function() { + // Open the archives popup + openArchives(); + + // Get the archives for this user + $('#archives .filter .friend').val(xid); + updateArchives(); + }).show(); + } + + // We catch the user's informations (like this avatar, vcard, and so on...) + getUserInfos(hash, xid, nick, type); + + // The icons-hover functions + tooltipIcons(xid, hash); + + // The event handlers + var inputDetect = $('#page-engine #' + hash + ' .message-area'); + + inputDetect.focus(function() { + chanCleanNotify(hash); + }) + + inputDetect.keypress(function(e) { + // Enter key + if(e.keyCode == 13) { + // Add a new line + if(e.shiftKey) + inputDetect.val(inputDetect.val() + '\n'); + + // Send the message + else { + // Send the message + sendMessage(hash, 'chat'); + + // Reset the composing database entry + setDB('chatstate', xid, 'off'); + } + + return false; + } + }); + + // Chatstate events + eventsChatState(inputDetect, xid, hash); +} diff --git a/jappixmini/jappix/js/chatstate.js b/jappixmini/jappix/js/chatstate.js new file mode 100644 index 00000000..de7e7966 --- /dev/null +++ b/jappixmini/jappix/js/chatstate.js @@ -0,0 +1,174 @@ +/* + +Jappix - An open social platform +These are the chatstate JS script for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 25/08/11 + +*/ + +// Sends a given chatstate to a given entity +function chatStateSend(state, xid, hash) { + var user_type = $('#' + hash).attr('data-type'); + + // If the friend client supports chatstates and is online + if((user_type == 'groupchat') || ((user_type == 'chat') && $('#' + hash + ' .message-area').attr('data-chatstates') && !exists('#page-switch .' + hash + ' .unavailable'))) { + // Already sent? + if(getDB('currentchatstate', xid) == state) + return; + + // Write the state + setDB('currentchatstate', xid, state); + + // New message stanza + var aMsg = new JSJaCMessage(); + aMsg.setTo(xid); + aMsg.setType(user_type); + + // Append the chatstate node + aMsg.appendNode(state, {'xmlns': NS_CHATSTATES}); + + // Send this! + con.send(aMsg); + } +} + +// Displays a given chatstate in a given chat +function displayChatState(state, hash, type) { + // Groupchat? + if(type == 'groupchat') { + resetChatState(hash, type); + + // "gone" state not allowed + if(state != 'gone') + $('#page-engine .page-engine-chan .user.' + hash).addClass(state); + } + + // Chat + else { + // We change the buddy name color in the page-switch + resetChatState(hash, type); + $('#page-switch .' + hash + ' .name').addClass(state); + + // We generate the chatstate text + var text = ''; + + switch(state) { + // Active + case 'active': + text = _e("Your friend is paying attention to the conversation."); + + break; + + // Composing + case 'composing': + text = _e("Your friend is writing a message..."); + + break; + + // Paused + case 'paused': + text = _e("Your friend stopped writing a message."); + + break; + + // Inactive + case 'inactive': + text = _e("Your friend is doing something else."); + + break; + + // Gone + case 'gone': + text = _e("Your friend closed the chat."); + + break; + } + + // We reset the previous state + $('#' + hash + ' .chatstate').remove(); + + // We create the chatstate + $('#' + hash + ' .content').after('
' + text + '
'); + } +} + +// Resets the chatstate switcher marker +function resetChatState(hash, type) { + // Define the selector + var selector; + + if(type == 'groupchat') + selector = $('#page-engine .page-engine-chan .user.' + hash); + else + selector = $('#page-switch .' + hash + ' .name'); + + // Reset! + selector.removeClass('active') + selector.removeClass('composing') + selector.removeClass('paused') + selector.removeClass('inactive') + selector.removeClass('gone'); +} + +// Adds the chatstate events +function eventsChatState(target, xid, hash) { + target.keyup(function(e) { + if(e.keyCode != 13) { + // Composing a message + if($(this).val() && (getDB('chatstate', xid) != 'on')) { + // We change the state detect input + setDB('chatstate', xid, 'on'); + + // We send the friend a "composing" chatstate + chatStateSend('composing', xid, hash); + } + + // Stopped composing a message + else if(!$(this).val() && (getDB('chatstate', xid) == 'on')) { + // We change the state detect input + setDB('chatstate', xid, 'off'); + + // We send the friend an "active" chatstate + chatStateSend('active', xid, hash); + } + } + }); + + target.change(function() { + // Reset the composing database entry + setDB('chatstate', xid, 'off'); + }); + + target.focus(function() { + // Not needed + if(target.is(':disabled')) + return; + + // Nothing in the input, user is active + if(!$(this).val()) + chatStateSend('active', xid, hash); + + // Something was written, user started writing again + else + chatStateSend('composing', xid, hash); + }); + + target.blur(function() { + // Not needed + if(target.is(':disabled')) + return; + + // Nothing in the input, user is inactive + if(!$(this).val()) + chatStateSend('inactive', xid, hash); + + // Something was written, user paused + else + chatStateSend('paused', xid, hash); + }); +} diff --git a/jappixmini/jappix/js/common.js b/jappixmini/jappix/js/common.js new file mode 100644 index 00000000..ab10d6e7 --- /dev/null +++ b/jappixmini/jappix/js/common.js @@ -0,0 +1,311 @@ +/* + +Jappix - An open social platform +These are the common JS script for Jappix + +------------------------------------------------- + +License: AGPL +Authors: Vanaryon, olivierm +Last revision: 24/06/11 + +*/ + +// Checks if an element exists in the DOM +function exists(selector) { + if(jQuery(selector).size() > 0) + return true; + else + return false; +} + +// Checks if Jappix is connected +function isConnected() { + if((typeof con != 'undefined') && con && con.connected()) + return true; + + return false; +} + +// Checks if Jappix has focus +function isFocused() { + try { + if(document.hasFocus()) + return true; + + return false; + } + + catch(e) { + return true; + } +} + +// Generates the good XID +function generateXID(xid, type) { + // XID needs to be transformed + if(xid && (xid.indexOf('@') == -1)) { + // Groupchat + if(type == 'groupchat') + return xid + '@' + HOST_MUC; + + // One-to-one chat + if(xid.indexOf('.') == -1) + return xid + '@' + HOST_MAIN; + + // It might be a gateway? + return xid; + } + + // Nothing special (yet bare XID) + return xid; +} + +// Gets the asked translated string +function _e(string) { + return string; +} + +// Replaces '%s' to a given value for a translated string +function printf(string, value) { + return string.replace('%s', value); +} + +// Properly explodes a string with a given character +function explodeThis(toEx, toStr, i) { + // Get the index of our char to explode + var index = toStr.indexOf(toEx); + + // We split if necessary the string + if(index != -1) { + if(i == 0) + toStr = toStr.substr(0, index); + else + toStr = toStr.substr(index + 1); + } + + // We return the value + return toStr; +} + +// Cuts the resource of a XID +function cutResource(aXID) { + return explodeThis('/', aXID, 0); +} + +// Gets the resource of a XID +function thisResource(aXID) { + // Any resource? + if(aXID.indexOf('/') != -1) + return explodeThis('/', aXID, 1); + + // No resource + return ''; +} + +// Does stringprep on a string +function stringPrep(string) { + // Replacement arrays + var invalid = new Array('Š', 'š', 'Đ', 'đ', 'Ž', 'ž', 'Č', 'č', 'Ć', 'ć', 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'Þ', 'ß', 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 'ø', 'ù', 'ú', 'û', 'ý', 'þ', 'ÿ', 'Ŕ', 'ŕ'); + + var valid = new Array('S', 's', 'Dj', 'dj', 'Z', 'z', 'C', 'c', 'C', 'c', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'C', 'E', 'E', 'E', 'E', 'I', 'I', 'I', 'I', 'N', 'O', 'O', 'O', 'O', 'O', 'O', 'U', 'U', 'U', 'U', 'Y', 'B', 'Ss', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'c', 'e', 'e', 'e', 'e', 'i', 'i', 'i', 'i', 'o', 'n', 'o', 'o', 'o', 'o', 'o', 'o', 'u', 'u', 'u', 'y', 'b', 'y', 'R', 'r'); + + // Compute a new string + for(i in invalid) + string = string.replace(invalid[i], valid[i]); + + return string; +} + +// Encodes quotes in a string +function encodeQuotes(str) { + return (str + '').replace(/"/g, '"'); +} + +// Gets the bare XID from a XID +function bareXID(xid) { + // Cut the resource + xid = cutResource(xid); + + // Launch the stringprep + xid = stringPrep(xid); + + // Set the XID to lower case + xid = xid.toLowerCase(); + + return xid; +} + +// Gets the full XID from a XID +function fullXID(xid) { + // Normalizes the XID + var full = bareXID(xid); + var resource = thisResource(xid); + + // Any resource? + if(resource) + full += '/' + resource; + + return full; +} + +// Gets the nick from a XID +function getXIDNick(aXID) { + return explodeThis('@', aXID, 0); +} + +// Gets the host from a XID +function getXIDHost(aXID) { + return explodeThis('@', aXID, 1); +} + +// Checks if we are in developer mode +function isDeveloper() { + if(DEVELOPER == 'on') + return true; + + return false; +} + +// Checks if anonymous mode is allowed +function allowedAnonymous() { + if(ANONYMOUS == 'on') + return true; + + return false; +} + +// Checks if host is locked +function lockHost() { + if(LOCK_HOST == 'on') + return true; + + return false; +} + +// Gets the full XID of the user +function getXID() { + // Return the XID of the user + if(con.username && con.domain) + return con.username + '@' + con.domain; + + return ''; +} + +// Generates the colors for a given user XID +function generateColor(xid) { + var colors = new Array( + 'ac0000', + 'a66200', + '007703', + '00705f', + '00236b', + '4e005c' + ); + + var number = 0; + + for(var i = 0; i < xid.length; i++) + number += xid.charCodeAt(i); + + var color = '#' + colors[number % (colors.length)]; + + return color; +} + +// Checks if the XID is a gateway +function isGateway(xid) { + if(xid.indexOf('@') != -1) + return false; + + return true; +} + +// Gets the from attribute of a stanza (overrides some servers like Prosody missing from attributes) +function getStanzaFrom(stanza) { + var from = stanza.getFrom(); + + // No from, we assume this is our XID + if(!from) + from = getXID(); + + return from; +} + +// Logs a given data in the console +function logThis(data, level) { + // Console not available + if(!isDeveloper() || (typeof(console) == 'undefined')) + return false; + + // Switch the log level + switch(level) { + // Debug + case 0: + console.debug(data); + + break; + + // Error + case 1: + console.error(data); + + break; + + // Warning + case 2: + console.warn(data); + + break; + + // Information + case 3: + console.info(data); + + break; + + // Default log level + default: + console.log(data); + + break; + } + + return true; +} + +// Gets the current Jappix app. location +function getJappixLocation() { + var url = window.location.href; + + // If the URL has variables, remove them + if(url.indexOf('?') != -1) + url = url.split('?')[0]; + if(url.indexOf('#') != -1) + url = url.split('#')[0]; + + // No "/" at the end + if(!url.match(/(.+)\/$/)) + url += '/'; + + return url; +} + +// Removes spaces at the beginning & the end of a string +function trim(str) { + return str.replace(/^\s+/g,'').replace(/\s+$/g,''); +} + +// Adds a zero to a date when needed +function padZero(i) { + // Negative number (without first 0) + if(i > -10 && i < 0) + return '-0' + (i * -1); + + // Positive number (without first 0) + if(i < 10 && i >= 0) + return '0' + i; + + // All is okay + return i; +} diff --git a/jappixmini/jappix/js/connection.js b/jappixmini/jappix/js/connection.js new file mode 100644 index 00000000..85a718c5 --- /dev/null +++ b/jappixmini/jappix/js/connection.js @@ -0,0 +1,526 @@ +/* + +Jappix - An open social platform +These are the connection JS script for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 29/08/11 + +*/ + +// Does the user login +var CURRENT_SESSION = false; + +function doLogin(lNick, lServer, lPass, lResource, lPriority, lRemember) { + try { + // We remove the not completed class to avoid problems + $('#home .loginer input').removeClass('please-complete'); + + // We add the login wait div + showGeneralWait(); + + // We define the http binding parameters + oArgs = new Object(); + + if(HOST_BOSH_MAIN) + oArgs.httpbase = HOST_BOSH_MAIN; + else + oArgs.httpbase = HOST_BOSH; + + // We create the new http-binding connection + con = new JSJaCHttpBindingConnection(oArgs); + + // And we handle everything that happen + setupCon(con); + + // Generate a resource + var random_resource = getDB('session', 'resource'); + + if(!random_resource) + random_resource = lResource + ' (' + (new Date()).getTime() + ')'; + + // We retrieve what the user typed in the login inputs + oArgs = new Object(); + oArgs.domain = trim(lServer); + oArgs.username = trim(lNick); + oArgs.resource = random_resource; + oArgs.pass = lPass; + oArgs.secure = true; + oArgs.xmllang = XML_LANG; + + // Store the resource (for reconnection) + setDB('session', 'resource', random_resource); + + // Generate a session XML to be stored + session_xml = 'true' + lServer.htmlEnc() + '' + lNick.htmlEnc() + '' + lResource.htmlEnc() + '' + lPass.htmlEnc() + '' + lPriority.htmlEnc() + ''; + + // Save the session parameters (for reconnect if network issue) + CURRENT_SESSION = session_xml; + + // Remember me? + if(lRemember) + setDB('remember', 'session', 1); + + // We store the infos of the user into the data-base + setDB('priority', 1, lPriority); + + // We connect ! + con.connect(oArgs); + + // Change the page title + pageTitle('wait'); + + logThis('Jappix is connecting...', 3); + } + + catch(e) { + // Logs errors + logThis('Error while logging in: ' + e, 1); + + // Reset Jappix + destroyTalkPage(); + + // Open an unknown error + openThisError(2); + } + + finally { + return false; + } +} + +// Handles the user registration +function handleRegistered() { + logThis('A new account has been registered.', 3); + + // We remove the waiting image + removeGeneralWait(); + + // Reset the title + pageTitle('home'); + + // We show the success information + $('#home .registerer .success').fadeIn('fast'); + + // We quit the session + logout(); +} + +// Does the user registration +function doRegister(username, domain, pass) { + logThis('Trying to register an account...', 3); + + try { + // We define the http binding parameters + oArgs = new Object(); + + if(HOST_BOSH_MAIN) + oArgs.httpbase = HOST_BOSH_MAIN; + else + oArgs.httpbase = HOST_BOSH; + + // We create the new http-binding connection + con = new JSJaCHttpBindingConnection(oArgs); + + // We setup the connection ! + con.registerHandler('onconnect', handleRegistered); + con.registerHandler('onerror', handleError); + + // We retrieve what the user typed in the register inputs + oArgs = new Object(); + oArgs.domain = trim(domain); + oArgs.username = trim(username); + oArgs.resource = JAPPIX_RESOURCE + ' Register (' + (new Date()).getTime() + ')'; + oArgs.pass = pass; + oArgs.register = true; + oArgs.secure = true; + oArgs.xmllang = XML_LANG; + + con.connect(oArgs); + + // We change the registered information text + $('#home .homediv.registerer').append( + '
' + + _e("You have been registered, here is your XMPP address:") + ' ' + con.username.htmlEnc() + '@' + con.domain.htmlEnc() + ' - ' + _e("Login") + '' + + '
' + ); + + // Login link + $('#home .homediv.registerer .success a').click(function() { + return doLogin(con.username, con.domain, con.pass, con.resource, '10', false); + }); + + // Show the waiting image + showGeneralWait(); + + // Change the page title + pageTitle('wait'); + } + + catch(e) { + // Logs errors + logThis(e, 1); + } + + finally { + return false; + } +} + +// Does the user anonymous login +function doAnonymous() { + logThis('Trying to login anonymously...', 3); + + var aPath = '#home .anonymouser '; + var room = $(aPath + '.room').val(); + var nick = $(aPath + '.nick').val(); + + // If the form is correctly completed + if(room && nick) { + // We remove the not completed class to avoid problems + $('#home .anonymouser input').removeClass('please-complete'); + + // Redirect the user to the anonymous room + window.location.href = JAPPIX_LOCATION + '?r=' + room + '&n=' + nick; + } + + // We check if the form is entirely completed + else { + $(aPath + 'input[type=text]').each(function() { + var select = $(this); + + if(!select.val()) + $(document).oneTime(10, function() { + select.addClass('please-complete').focus(); + }); + else + select.removeClass('please-complete'); + }); + } + + return false; +} + +// Handles the user connected event +var CONNECTED = false; + +function handleConnected() { + logThis('Jappix is now connected.', 3); + + // Connection markers + CONNECTED = true; + RECONNECT_TRY = 0; + RECONNECT_TIMER = 0; + + // We hide the home page + $('#home').hide(); + + // Not resumed? + if(!RESUME) { + // Remember the session? + if(getDB('remember', 'session')) + setPersistent('session', 1, CURRENT_SESSION); + + // We show the chatting app. + createTalkPage(); + + // We reset the homepage + switchHome('default'); + + // We get all the other things + getEverything(); + + // Set last activity stamp + LAST_ACTIVITY = getTimeStamp(); + } + + // Resumed + else { + // Send our presence + presenceSend(); + + // Change the title + updateTitle(); + } + + // Remove the waiting item + removeGeneralWait(); +} + +// Handles the user disconnected event +function handleDisconnected() { + logThis('Jappix is now disconnected.', 3); + + // Normal disconnection + if(!CURRENT_SESSION && !CONNECTED) + destroyTalkPage(); +} + +// Setups the normal connection +function setupCon(con) { + // We setup all the necessary handlers for the connection + con.registerHandler('message', handleMessage); + con.registerHandler('presence', handlePresence); + con.registerHandler('iq', handleIQ); + con.registerHandler('onconnect', handleConnected); + con.registerHandler('onerror', handleError); + con.registerHandler('ondisconnect', handleDisconnected); +} + +// Logouts from the server +function logout() { + // We are not connected + if(!isConnected()) + return false; + + // Disconnect from the XMPP server + con.disconnect(); + + logThis('Jappix is disconnecting...', 3); +} + +// Terminates a session +function terminate() { + if(!isConnected()) + return; + + // Clear temporary session storage + resetConMarkers(); + + // Show the waiting item (useful if BOSH is sloooow) + showGeneralWait(); + + // Change the page title + pageTitle('wait'); + + // Disconnect from the XMPP server + logout(); +} + +// Quitss a session +function quit() { + if(!isConnected()) + return; + + // We show the waiting image + showGeneralWait(); + + // Change the page title + pageTitle('wait'); + + // We disconnect from the XMPP server + logout(); +} + +// Creates the reconnect pane +var RECONNECT_TRY = 0; +var RECONNECT_TIMER = 0; + +function createReconnect(mode) { + logThis('This is not a normal disconnection, show the reconnect pane...', 1); + + // Reconnect pane not yet displayed? + if(!exists('#reconnect')) { + // Blur the focused input/textarea/select + $('input, select, textarea').blur(); + + // Create the HTML code + var html = '
' + + '
' + + _e("Due to a network issue, you were disconnected. What do you want to do now?"); + + // Can we cancel reconnection? + if(mode == 'normal') + html += '' + _e("Cancel") + ''; + + html += '' + _e("Reconnect") + '' + + '
'; + + // Append the code + $('body').append(html); + + // Click events + if(mode == 'normal') + $('#reconnect a.finish.cancel').click(function() { + return cancelReconnect(); + }); + + $('#reconnect a.finish.reconnect').click(function() { + return acceptReconnect(mode); + }); + + // Try to reconnect automatically after a while + if(RECONNECT_TRY < 5) + RECONNECT_TIMER = 5 + (5 * RECONNECT_TRY); + else + RECONNECT_TIMER = 120; + + // Change the try number + RECONNECT_TRY++; + + // Fire the event! + $('#reconnect a.finish.reconnect').everyTime('1s', function() { + // We can reconnect! + if(RECONNECT_TIMER == 0) + return acceptReconnect(mode); + + // Button text + if(RECONNECT_TIMER <= 10) + $(this).text(_e("Reconnect") + ' (' + RECONNECT_TIMER + ')'); + + // Remove 1 second + RECONNECT_TIMER--; + }); + + // Page title + updateTitle(); + } +} + +// Reconnects the user if he was disconnected (network issue) +var RESUME = false; + +function acceptReconnect(mode) { + logThis('Trying to reconnect the user...', 3); + + // Resume marker + RESUME = true; + + // Show waiting item + showGeneralWait(); + + // Reset some various stuffs + var groupchats = '#page-engine .page-engine-chan[data-type=groupchat]'; + $(groupchats + ' .list .role').hide(); + $(groupchats + ' .one-group, ' + groupchats + ' .list .user').remove(); + $(groupchats).attr('data-initial', 'false'); + + // Stop the timer + $('#reconnect a.finish.reconnect').stopTime(); + + // Remove the reconnect pane + $('#reconnect').remove(); + + // Try to login again + if(mode == 'normal') + loginFromSession(XMLFromString(CURRENT_SESSION)); + else if(mode == 'anonymous') + anonymousLogin(HOST_ANONYMOUS); + + return false; +} + +// Cancel the reconnection of user account (network issue) +function cancelReconnect() { + logThis('User has canceled automatic reconnection...', 3); + + // Stop the timer + $('#reconnect a.finish.reconnect').stopTime(); + + // Remove the reconnect pane + $('#reconnect').remove(); + + // Destroy the talk page + destroyTalkPage(); + + // Renitialize the previous session parameters + resetConMarkers(); + + return false; +} + +// Clears session reminder database +function clearLastSession() { + // Clear temporary storage + resetConMarkers(); + + // Clear persistent storage + if($(XMLFromString(getPersistent('session', 1))).find('stored').text() == 'true') + removePersistent('session', 1); +} + +// Resets the connection markers +function resetConMarkers() { + CURRENT_SESSION = false; + CONNECTED = false; + RESUME = false; + RECONNECT_TRY = 0; + RECONNECT_TIMER = 0; +} + +// Logins from a saved session +function loginFromSession(data) { + // Select the data + var session = $(data); + + // Fire the login event + doLogin( + session.find('username').text(), + session.find('domain').text(), + session.find('password').text(), + session.find('resource').text(), + session.find('priority').text(), + false + ); +} + +// Quits a session normally +function normalQuit() { + // Reset our database + clearLastSession(); + + // We quit the current session + quit(); + + // We show an info + openThisInfo(3); + + return false; +} + +// Gets all the users stuffs +function getEverything() { + getFeatures(); + getRoster(); + listPrivacy(); + getStorage(NS_ROSTERNOTES); +} + +// Plugin launcher +function launchConnection() { + // Logouts when Jappix is closed + $(window).bind('beforeunload', terminate); + + // Nothing to do when anonymous! + if(isAnonymous()) + return; + + // Try to resume a stored session, if not anonymous + var session = XMLFromString(getPersistent('session', 1)); + + if($(session).find('stored').text() == 'true') { + // Hide the homepage + $('#home').hide(); + + // Show the waiting icon + showGeneralWait(); + + // Login! + loginFromSession(session); + + logThis('Saved session found, resuming it...', 3); + } + + // Not connected, maybe a XMPP link is submitted? + else if((parent.location.hash != '#OK') && LINK_VARS['x']) { + switchHome('loginer'); + + logThis('A XMPP link is set, switch to login page.', 3); + } +} + +// Launch this plugin! +$(document).ready(launchConnection); diff --git a/jappixmini/jappix/js/constants.js b/jappixmini/jappix/js/constants.js new file mode 100644 index 00000000..bc59bd75 --- /dev/null +++ b/jappixmini/jappix/js/constants.js @@ -0,0 +1,211 @@ +/* + +Jappix - An open social platform +These are the constants JS scripts for Jappix + +------------------------------------------------- + +License: AGPL +Authors: Stefan Strigler, Vanaryon +Last revision: 26/08/11 + +*/ + +// XMPP XMLNS attributes +var NS_PROTOCOL = 'http://jabber.org/protocol/'; +var NS_FEATURES = 'http://jabber.org/features/'; +var NS_CLIENT = 'jabber:client'; +var NS_IQ = 'jabber:iq:'; +var NS_X = 'jabber:x:'; +var NS_IETF = 'urn:ietf:params:xml:ns:xmpp-'; +var NS_XMPP = 'urn:xmpp:'; + +var NS_STORAGE = 'storage:'; +var NS_BOOKMARKS = NS_STORAGE + 'bookmarks'; +var NS_ROSTERNOTES = NS_STORAGE + 'rosternotes'; + +var NS_JAPPIX = 'jappix:'; +var NS_INBOX = NS_JAPPIX + 'inbox'; +var NS_OPTIONS = NS_JAPPIX + 'options'; + +var NS_DISCO_ITEMS = NS_PROTOCOL + 'disco#items'; +var NS_DISCO_INFO = NS_PROTOCOL + 'disco#info'; +var NS_VCARD = 'vcard-temp'; +var NS_VCARD_P = NS_VCARD + ':x:update'; +var NS_AUTH = NS_IQ + 'auth'; +var NS_AUTH_ERROR = NS_IQ + 'auth:error'; +var NS_REGISTER = NS_IQ + 'register'; +var NS_SEARCH = NS_IQ + 'search'; +var NS_ROSTER = NS_IQ + 'roster'; +var NS_PRIVACY = NS_IQ + 'privacy'; +var NS_PRIVATE = NS_IQ + 'private'; +var NS_VERSION = NS_IQ + 'version'; +var NS_TIME = NS_IQ + 'time'; +var NS_LAST = NS_IQ + 'last'; +var NS_IQDATA = NS_IQ + 'data'; +var NS_XDATA = NS_X + 'data'; +var NS_IQOOB = NS_IQ + 'oob'; +var NS_XOOB = NS_X + 'oob'; +var NS_DELAY = NS_X + 'delay'; +var NS_EXPIRE = NS_X + 'expire'; +var NS_EVENT = NS_X + 'event'; +var NS_XCONFERENCE = NS_X + 'conference'; +var NS_STATS = NS_PROTOCOL + 'stats'; +var NS_MUC = NS_PROTOCOL + 'muc'; +var NS_MUC_USER = NS_MUC + '#user'; +var NS_MUC_ADMIN = NS_MUC + '#admin'; +var NS_MUC_OWNER = NS_MUC + '#owner'; +var NS_MUC_CONFIG = NS_MUC + '#roomconfig'; +var NS_PUBSUB = NS_PROTOCOL + 'pubsub'; +var NS_PUBSUB_EVENT = NS_PUBSUB + '#event'; +var NS_PUBSUB_OWNER = NS_PUBSUB + '#owner'; +var NS_PUBSUB_NMI = NS_PUBSUB + '#node-meta-info'; +var NS_PUBSUB_NC = NS_PUBSUB + '#node_config'; +var NS_PUBSUB_RI = NS_PUBSUB + '#retrieve-items'; +var NS_COMMANDS = NS_PROTOCOL + 'commands'; +var NS_BOSH = NS_PROTOCOL + 'httpbind'; +var NS_STREAM = 'http://etherx.jabber.org/streams'; +var NS_URN_TIME = NS_XMPP + 'time'; +var NS_URN_PING = NS_XMPP + 'ping'; +var NS_URN_ADATA = NS_XMPP + 'avatar:data'; +var NS_URN_AMETA = NS_XMPP + 'avatar:metadata'; +var NS_URN_MBLOG = NS_XMPP + 'microblog:0'; +var NS_URN_INBOX = NS_XMPP + 'inbox'; +var NS_URN_ARCHIVE = NS_XMPP + 'archive'; +var NS_URN_AR_PREF = NS_URN_ARCHIVE + ':pref'; +var NS_URN_AR_AUTO = NS_URN_ARCHIVE + ':auto'; +var NS_URN_AR_MANUAL = NS_URN_ARCHIVE + ':manual'; +var NS_URN_AR_MANAGE = NS_URN_ARCHIVE + ':manage'; +var NS_URN_DELAY = NS_XMPP + 'delay'; +var NS_URN_RECEIPTS = NS_XMPP + 'receipts'; +var NS_RSM = NS_PROTOCOL + 'rsm'; +var NS_IPV6 = 'ipv6'; +var NS_XHTML = 'http://www.w3.org/1999/xhtml'; +var NS_XHTML_IM = NS_PROTOCOL + 'xhtml-im'; +var NS_CHATSTATES = NS_PROTOCOL + 'chatstates'; +var NS_HTTP_AUTH = NS_PROTOCOL + 'http-auth'; +var NS_ROSTERX = NS_PROTOCOL + 'rosterx'; +var NS_MOOD = NS_PROTOCOL + 'mood'; +var NS_ACTIVITY = NS_PROTOCOL + 'activity'; +var NS_TUNE = NS_PROTOCOL + 'tune'; +var NS_GEOLOC = NS_PROTOCOL + 'geoloc'; +var NS_NICK = NS_PROTOCOL + 'nick'; +var NS_NOTIFY = '+notify'; +var NS_CAPS = NS_PROTOCOL + 'caps'; +var NS_ATOM = 'http://www.w3.org/2005/Atom'; + +var NS_STANZAS = NS_IETF + 'stanzas'; +var NS_STREAMS = NS_IETF + 'streams'; + +var NS_TLS = NS_IETF + 'tls'; +var NS_SASL = NS_IETF + 'sasl'; +var NS_SESSION = NS_IETF + 'session'; +var NS_BIND = NS_IETF + 'bind'; + +var NS_FEATURE_IQAUTH = NS_FEATURES + 'iq-auth'; +var NS_FEATURE_IQREGISTER = NS_FEATURES + 'iq-register'; +var NS_FEATURE_COMPRESS = NS_FEATURES + 'compress'; + +var NS_COMPRESS = NS_PROTOCOL + 'compress'; + +// Available locales +var LOCALES_AVAILABLE_ID = new Array(); +var LOCALES_AVAILABLE_NAMES = new Array(); + +// XML lang +var XML_LANG = null; + +// Jappix parameters +var JAPPIX_STATIC = null; +var JAPPIX_VERSION = null; +var JAPPIX_MAX_FILE_SIZE = null; +var JAPPIX_MAX_UPLOAD = null; + +// Jappix main configuration +var SERVICE_NAME = null; +var SERVICE_DESC = null; +var JAPPIX_RESOURCE = null; +var LOCK_HOST = null; +var ANONYMOUS = null; +var REGISTRATION = null; +var BOSH_PROXY = null; +var MANAGER_LINK = null; +var GROUPCHATS_JOIN = null; +var ENCRYPTION = null; +var HTTPS_STORAGE = null; +var HTTPS_FORCE = null; +var COMPRESSION = null; +var MULTI_FILES = null; +var DEVELOPER = null; + +// Jappix hosts configuration +var HOST_MAIN = null; +var HOST_MUC = null; +var HOST_PUBSUB = null; +var HOST_VJUD = null; +var HOST_ANONYMOUS = null; +var HOST_BOSH = null; +var HOST_BOSH_MAIN = null; +var HOST_BOSH_MINI = null; +var HOST_STATIC = null; +var HOST_UPLOAD = null; + +// Anonymous mode +var ANONYMOUS_ROOM = null; +var ANONYMOUS_NICK = null; + +// Node parameters +var JAPPIX_LOCATION = getJappixLocation(); + +// XMPP error stanzas +function STANZA_ERROR(code, type, cond) { + if (window == this) + return new STANZA_ERROR(code, type, cond); + + this.code = code; + this.type = type; + this.cond = cond; +} + +var ERR_BAD_REQUEST = + STANZA_ERROR('400', 'modify', 'bad-request'); +var ERR_CONFLICT = + STANZA_ERROR('409', 'cancel', 'conflict'); +var ERR_FEATURE_NOT_IMPLEMENTED = + STANZA_ERROR('501', 'cancel', 'feature-not-implemented'); +var ERR_FORBIDDEN = + STANZA_ERROR('403', 'auth', 'forbidden'); +var ERR_GONE = + STANZA_ERROR('302', 'modify', 'gone'); +var ERR_INTERNAL_SERVER_ERROR = + STANZA_ERROR('500', 'wait', 'internal-server-error'); +var ERR_ITEM_NOT_FOUND = + STANZA_ERROR('404', 'cancel', 'item-not-found'); +var ERR_JID_MALFORMED = + STANZA_ERROR('400', 'modify', 'jid-malformed'); +var ERR_NOT_ACCEPTABLE = + STANZA_ERROR('406', 'modify', 'not-acceptable'); +var ERR_NOT_ALLOWED = + STANZA_ERROR('405', 'cancel', 'not-allowed'); +var ERR_NOT_AUTHORIZED = + STANZA_ERROR('401', 'auth', 'not-authorized'); +var ERR_PAYMENT_REQUIRED = + STANZA_ERROR('402', 'auth', 'payment-required'); +var ERR_RECIPIENT_UNAVAILABLE = + STANZA_ERROR('404', 'wait', 'recipient-unavailable'); +var ERR_REDIRECT = + STANZA_ERROR('302', 'modify', 'redirect'); +var ERR_REGISTRATION_REQUIRED = + STANZA_ERROR('407', 'auth', 'registration-required'); +var ERR_REMOTE_SERVER_NOT_FOUND = + STANZA_ERROR('404', 'cancel', 'remote-server-not-found'); +var ERR_REMOTE_SERVER_TIMEOUT = + STANZA_ERROR('504', 'wait', 'remote-server-timeout'); +var ERR_RESOURCE_CONSTRAINT = + STANZA_ERROR('500', 'wait', 'resource-constraint'); +var ERR_SERVICE_UNAVAILABLE = + STANZA_ERROR('503', 'cancel', 'service-unavailable'); +var ERR_SUBSCRIPTION_REQUIRED = + STANZA_ERROR('407', 'auth', 'subscription-required'); +var ERR_UNEXPECTED_REQUEST = + STANZA_ERROR('400', 'wait', 'unexpected-request'); diff --git a/jappixmini/jappix/js/dataform.js b/jappixmini/jappix/js/dataform.js new file mode 100644 index 00000000..7fbea89d --- /dev/null +++ b/jappixmini/jappix/js/dataform.js @@ -0,0 +1,921 @@ +/* + +Jappix - An open social platform +These are the dataform JS scripts for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 28/08/11 + +*/ + +// Gets the defined dataform elements +function dataForm(host, type, node, action, target) { + // Clean the current session + cleanDataForm(target); + + // We tell the user that a search has been launched + $('#' + target + ' .wait').show(); + + // If we have enough data + if(host && type) { + // Generate a session ID + var sessionID = Math.round(100000.5 + (((900000.49999) - (100000.5)) * Math.random())); + var id = target + '-' + sessionID + '-' + genID(); + $('.' + target + '-results').attr('data-session', target + '-' + sessionID); + + // We request the service item + var iq = new JSJaCIQ(); + iq.setID(id); + iq.setTo(host); + iq.setType('get'); + + // MUC admin query + if(type == 'muc') { + iq.setQuery(NS_MUC_OWNER); + con.send(iq, handleDataFormMuc); + } + + // Browse query + else if(type == 'browse') { + var iqQuery = iq.setQuery(NS_DISCO_ITEMS); + + if(node) + iqQuery.setAttribute('node', node); + + con.send(iq, handleDataFormBrowse); + } + + // Command + else if(type == 'command') { + var items; + + if(node) + items = iq.appendNode('command', {'node': node, 'xmlns': NS_COMMANDS}); + + else { + items = iq.setQuery(NS_DISCO_ITEMS); + items.setAttribute('node', NS_COMMANDS); + } + + if(action && node) { + iq.setType('set'); + items.setAttribute('action', action); + } + + con.send(iq, handleDataFormCommand); + } + + // Search query + else if(type == 'search') { + iq.setQuery(NS_SEARCH); + con.send(iq, handleDataFormSearch); + } + + // Subscribe query + else if(type == 'subscribe') { + iq.setQuery(NS_REGISTER); + con.send(iq, handleDataFormSubscribe); + } + + // Join + else if(type == 'join') { + if(target == 'discovery') + closeDiscovery(); + + checkChatCreate(host, 'groupchat'); + } + } + + return false; +} + +// Sends a given dataform +function sendDataForm(type, action, x_type, id, xid, node, sessionid, target) { + // Path + var pathID = '#' + target + ' .results[data-session=' + id + ']'; + + // New IQ + var iq = new JSJaCIQ(); + iq.setTo(xid); + iq.setType('set'); + + // Set the correct query + var query; + + if(type == 'subscribe') + iqQuery = iq.setQuery(NS_REGISTER); + else if(type == 'search') + iqQuery = iq.setQuery(NS_SEARCH); + else if(type == 'command') + iqQuery = iq.appendNode('command', {'xmlns': NS_COMMANDS, 'node': node, 'sessionid': sessionid, 'action': action}); + else if(type == 'x') + iqQuery = iq.setQuery(NS_MUC_OWNER); + + // Build the XML document + if(action != 'cancel') { + // No X node + if(exists('input.register-special') && (type == 'subscribe')) { + $('input.register-special').each(function() { + var iName = $(this).attr('name'); + var iValue = $(this).val(); + + iqQuery.appendChild(iq.buildNode(iName, {'xmlns': NS_REGISTER}, iValue)); + }); + } + + // Can create the X node + else { + var iqX = iqQuery.appendChild(iq.buildNode('x', {'xmlns': NS_XDATA, 'type': x_type})); + + // Each input + $(pathID + ' .oneresult input, ' + pathID + ' .oneresult textarea, ' + pathID + ' .oneresult select').each(function() { + // Get the current input value + var iVar = $(this).attr('name'); + var iType = $(this).attr('data-type'); + var iValue = $(this).val(); + + // Build a new field node + var field = iqX.appendChild(iq.buildNode('field', {'var': iVar, 'type': iType, 'xmlns': NS_XDATA})); + + // Boolean input? + if(iType == 'boolean') { + if($(this).filter(':checked').size()) + iValue = '1'; + else + iValue = '0'; + } + + // JID-multi input? + if(iType == 'jid-multi') { + // Values array + var xid_arr = [iValue]; + var xid_check = []; + + // Try to split it + if(iValue.indexOf(',') != -1) + xid_arr = iValue.split(','); + + // Append each value to the XML document + for(i in xid_arr) { + // Get the current value + xid_current = trim(xid_arr[i]); + + // No current value? + if(!xid_current) + continue; + + // Add the current value + if(!existArrayValue(xid_check, xid_current)) { + xid_check.push(xid_current); + field.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, xid_current)); + } + } + } + + // List-multi selector? + else if(iType == 'list-multi') { + // Any value? + if(iValue && iValue.length) { + for(i in iValue) + field.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, iValue[i])); + } + } + + // Other inputs? + else + field.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, iValue)); + }); + } + } + + // Clean the current session + cleanDataForm(target); + + // Show the waiting item + $('#' + target + ' .wait').show(); + + // Change the ID of the current discovered item + var iqID = target + '-' + genID(); + $('#' + target + ' .' + target + '-results').attr('data-session', iqID); + iq.setID(iqID); + + // Send the IQ + if(type == 'subscribe') + con.send(iq, handleDataFormSubscribe); + else if(type == 'search') + con.send(iq, handleDataFormSearch); + else if(type == 'command') + con.send(iq, handleDataFormCommand); + else + con.send(iq); + + return false; +} + +// Displays the good dataform buttons +function buttonsDataForm(type, action, id, xid, node, sessionid, target, pathID) { + // No need to use buttons? + if(type == 'muc') + return; + + // Override the "undefined" output + if(!id) + id = ''; + if(!xid) + xid = ''; + if(!node) + node = ''; + if(!sessionid) + sessionid = ''; + + // We generate the buttons code + var buttonsCode = '
'; + + if(action == 'submit') { + if((target == 'adhoc') && (type == 'command')) { + buttonsCode += '' + _e("Submit") + ''; + + // When keyup on one text input + $(pathID + ' input').keyup(function(e) { + if(e.keyCode == 13) { + sendDataForm(type, 'execute', 'submit', id, xid, node, sessionid, target); + + return false; + } + }); + } + + else { + buttonsCode += '' + _e("Submit") + ''; + + // When keyup on one text input + $(pathID + ' input').keyup(function(e) { + if(e.keyCode == 13) { + sendDataForm(type, 'submit', 'submit', id, xid, node, sessionid, target); + + return false; + } + }); + } + } + + if((action == 'submit') && (type != 'subscribe') && (type != 'search')) + buttonsCode += '' + _e("Cancel") + ''; + + if(((action == 'back') || (type == 'subscribe') || (type == 'search')) && (target == 'discovery')) + buttonsCode += '' + _e("Close") + ''; + + if((action == 'back') && ((target == 'welcome') || (target == 'directory'))) + buttonsCode += '' + _e("Previous") + ''; + + if((action == 'back') && (target == 'adhoc')) + buttonsCode += '' + _e("Previous") + ''; + + buttonsCode += '
'; + + // We display the buttons code + $(pathID).append(buttonsCode); + + // If no submit link, lock the form + if(!exists(pathID + ' a.submit')) + $(pathID + ' input, ' + pathID + ' textarea').attr('readonly', true); +} + +// Handles the MUC dataform +function handleDataFormMuc(iq) { + handleErrorReply(iq); + handleDataFormContent(iq, 'muc'); +} + +// Handles the browse dataform +function handleDataFormBrowse(iq) { + handleErrorReply(iq); + handleDataFormContent(iq, 'browse'); +} + +// Handles the command dataform +function handleDataFormCommand(iq) { + handleErrorReply(iq); + handleDataFormContent(iq, 'command'); +} + +// Handles the subscribe dataform +function handleDataFormSubscribe(iq) { + handleErrorReply(iq); + handleDataFormContent(iq, 'subscribe'); +} + +// Handles the search dataform +function handleDataFormSearch(iq) { + handleErrorReply(iq); + handleDataFormContent(iq, 'search'); +} + +// Handles the dataform content +function handleDataFormContent(iq, type) { + // Get the ID + var sID = iq.getID(); + + // Get the target + var splitted = sID.split('-'); + var target = splitted[0]; + var sessionID = target + '-' + splitted[1]; + var from = fullXID(getStanzaFrom(iq)); + var pathID = '#' + target + ' .results[data-session=' + sessionID + ']'; + + // If an error occured + if(!iq || (iq.getType() != 'result')) + noResultDataForm(pathID); + + // If we got something okay + else { + var handleXML = iq.getNode(); + + if(type == 'browse') { + if($(handleXML).find('item').attr('jid')) { + // Get the query node + var queryNode = $(handleXML).find('query').attr('node'); + + $(handleXML).find('item').each(function() { + // We parse the received xml + var itemHost = $(this).attr('jid'); + var itemNode = $(this).attr('node'); + var itemName = $(this).attr('name'); + var itemHash = hex_md5(itemHost); + + // Node + if(itemNode) + $(pathID).append( + '
' + + '
' + itemNode.htmlEnc() + '
' + + '
' + ); + + // Item + else if(queryNode && itemName) + $(pathID).append( + '
' + + '
' + itemName.htmlEnc() + '
' + + '
' + ); + + // Item with children + else { + // We display the waiting element + $(pathID + ' .disco-wait .disco-category-title').after( + '
' + + '
' + + '
' + itemHost + '
' + + '
' + _e("Requesting this service...") + '
' + + '
' + ); + + // We display the category + $('#' + target + ' .disco-wait').show(); + + // We ask the server what's the service type + getDataFormType(itemHost, itemNode, sessionID); + } + }); + } + + // Else, there are no items for this query + else + noResultDataForm(pathID); + } + + else if((type == 'muc') || (type == 'search') || (type == 'subscribe') || ((type == 'command') && $(handleXML).find('command').attr('xmlns'))) { + // Get some values + var xCommand = $(handleXML).find('command'); + var bNode = xCommand.attr('node'); + var bSession = xCommand.attr('sessionid'); + var bStatus = xCommand.attr('status'); + var xRegister = $(handleXML).find('query[xmlns=' + NS_REGISTER + ']').text(); + var xElement = $(handleXML).find('x'); + + // Search done + if((xElement.attr('type') == 'result') && (type == 'search')) { + var bPath = pathID; + + // Display the result + $(handleXML).find('item').each(function() { + var bXID = $(this).find('field[var=jid] value:first').text(); + var bName = $(this).find('field[var=fn] value:first').text(); + var bCountry = $(this).find('field[var=ctry] value:first').text(); + var dName = bName; + + // Override "undefined" value + if(!bXID) + bXID = ''; + if(!bName) + bName = _e("Unknown name"); + if(!bCountry) + bCountry = _e("Unknown country"); + + // User hash + var bHash = hex_md5(bXID); + + // HTML code + var bHTML = '
' + + '
' + + '' + + '
' + + '
' + bName + '
' + + '
' + bCountry + '
' + + '
' + bXID + '
' + + '
'; + + // The buddy is not in our buddy list? + if(!exists('#buddy-list .buddy[data-xid=' + escape(bXID) + ']')) + bHTML += '' + _e("Add") + ''; + + // Chat button, if not in welcome/directory mode + if(target == 'discovery') + bHTML += '' + _e("Chat") + ''; + + // Close the HTML element + bHTML += '
'; + + $(bPath).append(bHTML); + + // Click events + $(bPath + ' .' + bHash + ' a').click(function() { + // Buddy add + if($(this).is('.one-add')) { + $(this).hide(); + + addThisContact(bXID, dName); + } + + // Buddy chat + if($(this).is('.one-chat')) { + if(target == 'discovery') + closeDiscovery(); + + checkChatCreate(bXID , 'chat', '', '', dName); + } + + return false; + }); + + // Get the user's avatar + if(bXID) + getAvatar(bXID, 'cache', 'true', 'forget'); + }); + + // No result? + if(!$(handleXML).find('item').size()) + noResultDataForm(pathID); + + // Previous button + buttonsDataForm(type, 'back', sessionID, from, bNode, bSession, target, pathID); + } + + // Command to complete + else if(xElement.attr('xmlns') || ((type == 'subscribe') && xRegister)) { + // We display the elements + fillDataForm(handleXML, sessionID); + + // We display the buttons + if(bStatus != 'completed') + buttonsDataForm(type, 'submit', sessionID, from, bNode, bSession, target, pathID); + else + buttonsDataForm(type, 'back', sessionID, from, bNode, bSession, target, pathID); + } + + // Command completed or subscription done + else if(((bStatus == 'completed') && (type == 'command')) || (!xRegister && (type == 'subscribe'))) { + // Display the good text + var cNote = $(xCommand).find('note'); + + // Any note? + if(cNote.size()) { + cNote.each(function() { + $(pathID).append( + '
' + $(this).text().htmlEnc() + '
' + ); + }); + } + + // Default text + else + $(pathID).append('
' + _e("Your form has been sent.") + '
'); + + // Display the back button + buttonsDataForm(type, 'back', sessionID, from, '', '', target, pathID); + + // Add the gateway to our roster if subscribed + if(type == 'subscribe') + addThisContact(from); + } + + // Command canceled + else if((bStatus == 'canceled') && (type == 'command')) { + if(target == 'discovery') + startDiscovery(); + else if(target == 'adhoc') + dataForm(from, 'command', '', '', 'adhoc'); + } + + // No items for this query + else + noResultDataForm(pathID); + } + + else if(type == 'command') { + if($(handleXML).find('item').attr('jid')) { + // We display the elements + $(handleXML).find('item').each(function() { + // We parse the received xml + var itemHost = $(this).attr('jid'); + var itemNode = $(this).attr('node'); + var itemName = $(this).attr('name'); + var itemHash = hex_md5(itemHost); + + // We display the waiting element + $(pathID).prepend( + '
' + + '
' + itemName + '
' + + '
»
' + + '
' + ); + }); + } + + // Else, there are no items for this query + else + noResultDataForm(pathID); + } + } + + // Focus on the first input + $(document).oneTime(10, function() { + $(pathID + ' input:visible:first').focus(); + }); + + // Hide the wait icon + $('#' + target + ' .wait').hide(); +} + +// Fills the dataform elements +function fillDataForm(xml, id) { + /* REF: http://xmpp.org/extensions/xep-0004.html */ + + // Initialize new vars + var target = id.split('-')[0]; + var pathID = '#' + target + ' .results[data-session=' + id + ']'; + var selector, is_dataform; + + // Is it a dataform? + if($(xml).find('x[xmlns=' + NS_XDATA + ']').size()) + is_dataform = true; + else + is_dataform = false; + + // Determines the good selector to use + if(is_dataform) + selector = $(xml).find('x[xmlns=' + NS_XDATA + ']'); + else + selector = $(xml); + + // Form title + selector.find('title').each(function() { + $(pathID).append( + '
' + $(this).text().htmlEnc() + '
' + ); + }); + + // Form instructions + selector.find('instructions').each(function() { + $(pathID).append( + '
' + $(this).text().htmlEnc() + '
' + ); + }); + + // Register? + if(!is_dataform) { + // Items to detect + var reg_names = [_e("Nickname"), _e("Name"), _e("Password"), _e("E-mail")]; + var reg_ids = ['username', 'name', 'password', 'email']; + + // Append these inputs + for(a in reg_names) { + selector.find(reg_ids[a]).each(function() { + $(pathID).append( + '
' + + '' + + '' + + '
' + ); + }); + } + + return false; + } + + // Dataform? + selector.find('field').each(function() { + // We parse the received xml + var type = $(this).attr('type'); + var label = $(this).attr('label'); + var field = $(this).attr('var'); + var value = $(this).find('value:first').text(); + var required = ''; + + // No value? + if(!field) + return; + + // Required input? + if($(this).find('required').size()) + required = ' required=""'; + + // Compatibility fix + if(!label) + label = field; + + if(!type) + type = ''; + + // Generate some values + var input; + var hideThis = ''; + + // Fixed field + if(type == 'fixed') + $(pathID).append('
' + value.htmlEnc() + '
'); + + else { + // Hidden field + if(type == 'hidden') { + hideThis = ' style="display: none;"'; + input = ''; + } + + // Boolean field + else if(type == 'boolean') { + var checked; + + if(value == '1') + checked = 'checked'; + else + checked = ''; + + input = ''; + } + + // List-single/list-multi field + else if((type == 'list-single') || (type == 'list-multi')) { + var multiple = ''; + + // Multiple options? + if(type == 'list-multi') + multiple = ' multiple=""'; + + // Append the select field + input = ''; + } + + // Text-multi field + else if(type == 'text-multi') + input = ''; + + // JID-multi field + else if(type == 'jid-multi') { + // Put the XID into an array + var xid_arr = []; + + $(this).find('value').each(function() { + var cValue = $(this).text(); + + if(!existArrayValue(xid_arr, cValue)) + xid_arr.push(cValue); + }); + + // Sort the array + xid_arr.sort(); + + // Create the input + var xid_value = ''; + + if(xid_arr.length) { + for(i in xid_arr) { + // Any pre-value + if(xid_value) + xid_value += ', '; + + // Add the current XID + xid_value += xid_arr[i]; + } + } + + input = ''; + } + + // Other stuffs that are similar + else { + // Text-single field + var iType = 'text'; + + // Text-private field + if(type == 'text-private') + iType = 'password'; + + // JID-single field + else if(type == 'jid-single') + iType = 'email'; + + input = ''; + } + + // Append the HTML markup for this field + $(pathID).append( + '
' + + '' + + input + + '
' + ); + } + }); + + return false; +} + +// Gets the dataform type +function getDataFormType(host, node, id) { + var iq = new JSJaCIQ(); + iq.setID(id + '-' + genID()); + iq.setTo(host); + iq.setType('get'); + + var iqQuery = iq.setQuery(NS_DISCO_INFO); + + if(node) + iqQuery.setAttribute('node', node); + + con.send(iq, handleThisBrowse); +} + +// Handles the browse stanza +function handleThisBrowse(iq) { + /* REF: http://xmpp.org/registrar/disco-categories.html */ + + var id = iq.getID(); + var splitted = id.split('-'); + var target = splitted[0]; + var sessionID = target + '-' + splitted[1]; + var from = fullXID(getStanzaFrom(iq)); + var hash = hex_md5(from); + var handleXML = iq.getQuery(); + var pathID = '#' + target + ' .results[data-session=' + sessionID + ']'; + + // We first remove the waiting element + $(pathID + ' .disco-wait .' + hash).remove(); + + if($(handleXML).find('identity').attr('type')) { + var category = $(handleXML).find('identity').attr('category'); + var type = $(handleXML).find('identity').attr('type'); + var named = $(handleXML).find('identity').attr('name'); + + if(named) + gName = named; + else + gName = ''; + + var one, two, three, four, five; + + // Get the features that this entity supports + var findFeature = $(handleXML).find('feature'); + + for(i in findFeature) { + var current = findFeature.eq(i).attr('var'); + + switch(current) { + case NS_SEARCH: + one = 1; + break; + + case NS_MUC: + two = 1; + break; + + case NS_REGISTER: + three = 1; + break; + + case NS_COMMANDS: + four = 1; + break; + + case NS_DISCO_ITEMS: + five = 1; + break; + + default: + break; + } + } + + var buttons = Array(one, two, three, four, five); + + // We define the toolbox links depending on the supported features + var tools = ''; + var aTools = Array('search', 'join', 'subscribe', 'command', 'browse'); + var bTools = Array(_e("Search"), _e("Join"), _e("Subscribe"), _e("Command"), _e("Browse")); + + for(i in buttons) { + if(buttons[i]) + tools += ''; + } + + // As defined in the ref, we detect the type of each category to put an icon + switch(category) { + case 'account': + case 'auth': + case 'automation': + case 'client': + case 'collaboration': + case 'component': + case 'conference': + case 'directory': + case 'gateway': + case 'headline': + case 'hierarchy': + case 'proxy': + case 'pubsub': + case 'server': + case 'store': + break; + + default: + category = 'others'; + } + + // We display the item we found + $(pathID + ' .disco-' + category + ' .disco-category-title').after( + '
' + + '
' + + '
' + from + '
' + + '
' + gName + '
' + + '
' + tools + '
' + + '
' + ); + + // We display the category + $(pathID + ' .disco-' + category).show(); + } + + else { + $(pathID + ' .disco-others .disco-category-title').after( + '
' + + '
' + + '
' + from + '
' + + '
' + _e("Service offline or broken") + '
' + + '
' + ); + + // We display the category + $(pathID + ' .disco-others').show(); + } + + // We hide the waiting stuffs if there's no remaining loading items + if(!$(pathID + ' .disco-wait .' + target + '-oneresult').size()) + $(pathID + ' .disco-wait, #' + target + ' .wait').hide(); +} + +// Cleans the current data-form popup +function cleanDataForm(target) { + if(target == 'discovery') + cleanDiscovery(); + else + $('#' + target + ' div.results').empty(); +} + +// Displays the no result indicator +function noResultDataForm(path) { + $(path).prepend('

' + _e("Sorry, but the entity didn't return any result!") + '

'); +} diff --git a/jappixmini/jappix/js/datastore.js b/jappixmini/jappix/js/datastore.js new file mode 100644 index 00000000..b818b301 --- /dev/null +++ b/jappixmini/jappix/js/datastore.js @@ -0,0 +1,209 @@ +/* + +Jappix - An open social platform +These are the temporary/persistent data store functions + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 23/06/11 + +*/ + +// Temporary: returns whether it is available or not +function hasDB() { + if(window.sessionStorage) + return true; + + return false; +} + +// Temporary: used to read a database entry +function getDB(type, id) { + try { + return sessionStorage.getItem(type + '_' + id); + } + + catch(e) { + logThis('Error while getting a temporary database entry (' + type + ' -> ' + id + '): ' + e, 1); + + return null; + } +} + +// Temporary: used to update a database entry +function setDB(type, id, value) { + try { + sessionStorage.setItem(type + '_' + id, value); + + return true; + } + + catch(e) { + logThis('Error while writing a temporary database entry (' + type + ' -> ' + id + '): ' + e, 1); + + return false; + } +} + +// Temporary: used to remove a database entry +function removeDB(type, id) { + try { + sessionStorage.removeItem(type + '_' + id); + + return true; + } + + catch(e) { + logThis('Error while removing a temporary database entry (' + type + ' -> ' + id + '): ' + e, 1); + + return false; + } +} + +// Temporary: used to check a database entry exists +function existDB(type, id) { + var read = getDB(type, id); + + if(read != null) + return true; + + return false; +} + +// Temporary: used to clear all the database +function resetDB() { + try { + sessionStorage.clear(); + + logThis('Temporary database cleared.', 3); + + return true; + } + + catch(e) { + logThis('Error while clearing temporary database: ' + e, 1); + + return false; + } +} + +// Persistent: returns whether it is available or not +function hasPersistent() { + if(window.localStorage) + return true; + + return false; +} + +// Persistent: used to read a database entry +function getPersistent(type, id) { + try { + return localStorage.getItem(type + '_' + id); + } + + catch(e) { + logThis('Error while getting a persistent database entry (' + type + ' -> ' + id + '): ' + e, 1); + + return null; + } +} + +// Persistent: used to update a database entry +function setPersistent(type, id, value) { + try { + localStorage.setItem(type + '_' + id, value); + + return true; + } + + // Database might be full + catch(e) { + logThis('Retrying: could not write a persistent database entry (' + type + ' -> ' + id + '): ' + e, 2); + + // Flush it! + flushPersistent(); + + // Set the item again + try { + localStorage.setItem(type + '_' + id, value); + + return true; + } + + // New error! + catch(e) { + logThis('Aborted: error while writing a persistent database entry (' + type + ' -> ' + id + '): ' + e, 1); + + return false; + } + } +} + +// Persistent: used to remove a database entry +function removePersistent(type, id) { + try { + localStorage.removeItem(type + '_' + id); + + return true; + } + + catch(e) { + logThis('Error while removing a persistent database entry (' + type + ' -> ' + id + '): ' + e, 1); + + return false; + } +} + +// Persistent: used to check a database entry exists +function existPersistent(type, id) { + var read = getPersistent(type, id); + + if(read != null) + return true; + + return false; +} + +// Persistent: used to clear all the database +function resetPersistent() { + try { + localStorage.clear(); + + logThis('Persistent database cleared.', 3); + + return true; + } + + catch(e) { + logThis('Error while clearing persistent database: ' + e, 1); + + return false; + } +} + +// Persistent: used to flush the database +function flushPersistent() { + try { + // Get the stored session entry + var session = getPersistent('session', 1); + + // Clear the persistent database + localStorage.clear(); + + // Restaure the stored session entry + if(session) + setPersistent('session', 1, session); + + logThis('Persistent database flushed.', 3); + + return true; + } + + catch(e) { + logThis('Error while flushing persistent database: ' + e, 1); + + return false; + } +} diff --git a/jappixmini/jappix/js/date.js b/jappixmini/jappix/js/date.js new file mode 100644 index 00000000..86731aa1 --- /dev/null +++ b/jappixmini/jappix/js/date.js @@ -0,0 +1,212 @@ +/* + +Jappix - An open social platform +These are the date related JS scripts for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 17/08/11 + +*/ + +// Gets a stamp from a date +function extractStamp(date) { + return Math.round(date.getTime() / 1000); +} + +// Gets the time from a date +function extractTime(date) { + return date.toLocaleTimeString(); +} + +// Gets the actual date stamp +function getTimeStamp() { + return extractStamp(new Date()); +} + +// Gets the last user activity in seconds +var LAST_ACTIVITY = 0; + +function getLastActivity() { + // Last activity not yet initialized? + if(LAST_ACTIVITY == 0) + return 0; + + return getTimeStamp() - LAST_ACTIVITY; +} + +// Gets the last user available presence in seconds +var PRESENCE_LAST_ACTIVITY = 0; + +function getPresenceLast() { + // Last presence stamp not yet initialized? + if(PRESENCE_LAST_ACTIVITY == 0) + return 0; + + return getTimeStamp() - PRESENCE_LAST_ACTIVITY; +} + +// Generates the time for XMPP +function getXMPPTime(location) { + /* FROM : http://trac.jwchat.org/jsjac/browser/branches/jsjac_1.0/jsextras.js?rev=221 */ + + // Initialize + var jInit = new Date(); + var year, month, day, hours, minutes, seconds; + + // Gets the UTC date + if(location == 'utc') { + year = jInit.getUTCFullYear(); + month = jInit.getUTCMonth(); + day = jInit.getUTCDate(); + hours = jInit.getUTCHours(); + minutes = jInit.getUTCMinutes(); + seconds = jInit.getUTCSeconds(); + } + + // Gets the local date + else { + year = jInit.getFullYear(); + month = jInit.getMonth(); + day = jInit.getDate(); + hours = jInit.getHours(); + minutes = jInit.getMinutes(); + seconds = jInit.getSeconds(); + } + + // Generates the date string + var jDate = year + '-'; + jDate += padZero(month + 1) + '-'; + jDate += padZero(day) + 'T'; + jDate += padZero(hours) + ':'; + jDate += padZero(minutes) + ':'; + jDate += padZero(seconds) + 'Z'; + + // Returns the date string + return jDate; +} + +// Generates then human time +function getCompleteTime() { + var init = new Date(); + var time = padZero(init.getHours()) + ':'; + time += padZero(init.getMinutes()) + ':'; + time += padZero(init.getSeconds()); + + return time; +} + +// Gets the TZO of a date +function getDateTZO() { + // Get the date + var date = new Date(); + var offset = date.getTimezoneOffset(); + + // Default vars + var sign = ''; + var hours = 0; + var minutes = 0; + + // Process a neutral offset + if(offset < 0) { + offset = offset * -1; + sign = '+'; + } + + // Get the values + var n_date = new Date(offset * 60 * 1000); + hours = n_date.getHours() - 1; + minutes = n_date.getMinutes(); + + // Process the TZO + tzo = sign + padZero(hours) + ':' + padZero(minutes); + + // Return the processed value + return tzo; +} + +// Parses a XMPP date (yyyy-mm-dd, hh-mm-ss) into an human-readable one +function parseDate(to_parse) { + var date = Date.jab2date(to_parse); + var parsed = date.toLocaleDateString() + ' (' + date.toLocaleTimeString() + ')'; + + return parsed; +} + +// Parses a XMPP date (yyyy-mm-dd) into an human-readable one +function parseDay(to_parse) { + var date = Date.jab2date(to_parse); + var parsed = date.toLocaleDateString(); + + return parsed; +} + +// Parses a XMPP date (hh-mm-ss) into an human-readable one +function parseTime(to_parse) { + var date = Date.jab2date(to_parse); + var parsed = date.toLocaleTimeString(); + + return parsed; +} + +// Parses a XMPP date stamp into a relative one +function relativeDate(to_parse) { + // Get the current date + var current_date = Date.jab2date(getXMPPTime('utc')); + var current_day = current_date.getDate(); + var current_stamp = current_date.getTime(); + + // Parse the given date + var old_date = Date.jab2date(to_parse); + var old_day = old_date.getDate(); + var old_stamp = old_date.getTime(); + var old_time = old_date.toLocaleTimeString(); + + // Get the day number between the two dates + var days = Math.round((current_stamp - old_stamp) / 86400000); + + // Invalid date? + if(isNaN(old_stamp) || isNaN(days)) + return getCompleteTime(); + + // Is it today? + if(current_day == old_day) + return old_time; + + // It is yesterday? + if(days <= 1) + return _e("Yesterday") + ' - ' + old_time; + + // Is it less than a week ago? + if(days <= 7) + return printf(_e("%s days ago"), days) + ' - ' + old_time; + + // Another longer period + return old_date.toLocaleDateString() + ' - ' + old_time; +} + +// Reads a message delay +function readMessageDelay(node) { + // Initialize + var delay, d_delay; + + // Read the delay + d_delay = jQuery(node).find('delay[xmlns=' + NS_URN_DELAY + ']:first').attr('stamp'); + + // New delay (valid XEP) + if(d_delay) + delay = d_delay; + + // Old delay (obsolete XEP!) + else { + // Try to read the old-school delay + var x_delay = jQuery(node).find('x[xmlns=' + NS_DELAY + ']:first').attr('stamp'); + + if(x_delay) + delay = x_delay.replace(/^(\w{4})(\w{2})(\w{2})T(\w{2}):(\w{2}):(\w{2})Z?(\S+)?/, '$1-$2-$3T$4:$5:$6Z$7'); + } + + return delay; +} diff --git a/jappixmini/jappix/js/directory.js b/jappixmini/jappix/js/directory.js new file mode 100644 index 00000000..ebe1a570 --- /dev/null +++ b/jappixmini/jappix/js/directory.js @@ -0,0 +1,87 @@ +/* + +Jappix - An open social platform +These are the directory JS scripts for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 03/03/11 + +*/ + +// Opens the directory popup +function openDirectory() { + // Popup HTML content + var html = + '
' + _e("User directory") + '
' + + + '
' + + '
' + + '
' + _e("Server to query") + '
' + + + '' + + '
' + + + '
' + + '
' + + + '
' + + '
' + + + '' + _e("Close") + '' + + '
'; + + // Create the popup + createPopup('directory', html); + + // Associate the events + launchDirectory(); + + // Start a search! + startDirectory(); + + return false; +} + +// Quits the directory popup +function closeDirectory() { + // Destroy the popup + destroyPopup('directory'); + + return false; +} + +// Launches a directory search +function startDirectory() { + // Get the server to query + var server = $('#directory .directory-server-input').val(); + + // Launch the search! + dataForm($('#directory .directory-server-input').val(), 'search', '', '', 'directory'); + + logThis('Directory search launched: ' + server); + + return false; +} + +// Plugin launcher +function launchDirectory() { + // Click event + $('#directory .bottom .finish').click(closeDirectory); + + // Keyboard event + $('#directory .directory-server-input').keyup(function(e) { + if(e.keyCode == 13) { + // No value? + if(!$(this).val()) + $(this).val(HOST_VJUD); + + // Start the directory search + startDirectory(); + + return false; + } + }); +} diff --git a/jappixmini/jappix/js/discovery.js b/jappixmini/jappix/js/discovery.js new file mode 100644 index 00000000..867037b2 --- /dev/null +++ b/jappixmini/jappix/js/discovery.js @@ -0,0 +1,169 @@ +/* + +Jappix - An open social platform +These are the discovery JS scripts for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 03/03/11 + +*/ + +// Opens the discovery popup +function openDiscovery() { + // Popup HTML content + var html = + '
' + _e("Service discovery") + '
' + + + '
' + + '
' + + '
' + _e("Server to query") + '
' + + + '' + + '
' + + + '
' + + '' + + + '
' + + '

' + _e("Authentications") + '

' + + '
' + + + '
' + + '

' + _e("Automation") + '

' + + '
' + + + '
' + + '

' + _e("Clients") + '

' + + '
' + + + '
' + + '

' + _e("Collaboration") + '

' + + '
' + + + '
' + + '

' + _e("Components") + '

' + + '
' + + + '
' + + '

' + _e("Rooms") + '

' + + '
' + + + '
' + + '

' + _e("Directories") + '

' + + '
' + + + '
' + + '

' + _e("Gateways") + '

' + + '
' + + + '
' + + '

' + _e("News") + '

' + + '
' + + + '
' + + '

' + _e("Hierarchy") + '

' + + '
' + + + '
' + + '

' + _e("Proxies") + '

' + + '
' + + + '
' + + '

' + _e("Publication/Subscription") + '

' + + '
' + + + '
' + + '

' + _e("Server") + '

' + + '
' + + + '
' + + '

' + _e("Storage") + '

' + + '
' + + + '
' + + '

' + _e("Others") + '

' + + '
' + + + '
' + + '

' + _e("Loading") + '

' + + '
' + + '
' + + '
' + + + '
' + + '
' + + + '' + _e("Close") + '' + + '
'; + + // Create the popup + createPopup('discovery', html); + + // Associate the events + launchDiscovery(); + + // We request a disco to the default server + startDiscovery(); + + return false; +} + +// Quits the discovery popup +function closeDiscovery() { + // Destroy the popup + destroyPopup('discovery'); + + return false; +} + +// Launches a discovery +function startDiscovery() { + /* REF: http://xmpp.org/extensions/xep-0030.html */ + + // We get the server to query + var discoServer = $('#discovery .disco-server-input').val(); + + // We launch the items query + dataForm(discoServer, 'browse', '', '', 'discovery'); + + logThis('Service discovery launched: ' + discoServer); + + return false; +} + +// Cleans the discovery results +function cleanDiscovery() { + // We remove the results + $('#discovery .discovery-oneresult, #discovery .oneinstructions, #discovery .onetitle, #discovery .no-results').remove(); + + // We clean the user info + $('#discovery .disco-server-info').text(''); + + // We hide the wait icon, the no result alert and the results + $('#discovery .wait, #discovery .disco-category').hide(); +} + +// Plugin launcher +function launchDiscovery() { + // Click event + $('#discovery .bottom .finish').click(closeDiscovery); + + // Keyboard event + $('#discovery .disco-server-input').keyup(function(e) { + if(e.keyCode == 13) { + // No value? + if(!$(this).val()) + $(this).val(HOST_MAIN); + + // Start the discovery + startDiscovery(); + + return false; + } + }); +} diff --git a/jappixmini/jappix/js/error.js b/jappixmini/jappix/js/error.js new file mode 100644 index 00000000..c16ca2a5 --- /dev/null +++ b/jappixmini/jappix/js/error.js @@ -0,0 +1,139 @@ +/* + +Jappix - An open social platform +These are the error functions for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 02/04/11 + +*/ + +// Shows the given error output +function showError(condition, reason, type) { + // Enough data to output the error + if(condition || reason) { + // Initialize the error text + var eText = ''; + + // Any error condition + if(condition) + eText += condition; + + // Any error type + if(type && eText) + eText += ' (' + type + ')'; + + // Any error reason + if(reason) { + if(eText) + eText += ' - '; + + eText += reason; + } + + // We reveal the error + openThisError(1); + + // Create the error text + $('#board .one-board.error[data-id=1] span').text(eText); + } + + // Not enough data to output the error: output a generic board + else + openThisError(2); +} + +// Handles the error from a packet and return true if any error +function handleError(packet) { + /* REF: http://xmpp.org/extensions/xep-0086.html */ + + // Initialize + var type, code, reason, condition; + var node = $(packet); + + // First level error (connection error) + if(node.is('error')) { + // Get the value + code = node.attr('code'); + + // Specific error reason + switch(code) { + case '401': + reason = _e("Authorization failed"); + break; + + case '409': + reason = _e("Registration failed, please choose a different username"); + break; + + case '503': + reason = _e("Service unavailable"); + break; + + case '500': + reason = _e("Internal server error, try later"); + break; + + default: + reason = node.find('text').text(); + break; + } + + // Remove the general wait item (security) + removeGeneralWait(); + + // Show reconnect pane + if(CURRENT_SESSION && CONNECTED) { + // Anonymous? + if(isAnonymous()) + createReconnect('anonymous'); + else + createReconnect('normal'); + } + + // Show the homepage (security) + else if(!CURRENT_SESSION || !CONNECTED) { + $('#home').show(); + pageTitle('home'); + } + + // Still connected? (security) + if(isConnected()) + con.disconnect(); + + logThis('First level error received.', 1); + } + + // Second level error (another error) + else if(node.find('error').size()) { + type = node.find('error').attr('type'); + reason = node.find('error text').text(); + condition = packet.getElementsByTagName('error').item(0).childNodes.item(0).nodeName.replace(/-/g, ' '); + + logThis('Second level error received.', 1); + } + + // No error + else + return false; + + // Show the error board + showError(condition, reason, type); + + // Return there's an error + return true; +} + +// Handles the error reply of a packet +function handleErrorReply(packet) { + return handleError(packet.getNode()); +} + +// Handles the error reply for a message +function handleMessageError(packet) { + if(!handleErrorReply(packet)) + handleMessage(packet); +} diff --git a/jappixmini/jappix/js/favorites.js b/jappixmini/jappix/js/favorites.js new file mode 100644 index 00000000..69a042e8 --- /dev/null +++ b/jappixmini/jappix/js/favorites.js @@ -0,0 +1,537 @@ +/* + +Jappix - An open social platform +These are the favorites JS scripts for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 23/06/11 + +*/ + +// Opens the favorites popup +function openFavorites() { + // Popup HTML content + var html = + '
' + _e("Manage favorite rooms") + '
' + + + '
' + + '
' + + '
' + + '
' + + + _e("Change favorites") + + '
' + + + '' + + '
' + + + '
' + + '
' + + '
' + + '
' + _e("Select a favorite") + '
' + + + '' + + '
' + + + '
' + + '
' + + '' + + + '' + + '
' + + + '
' + + '' + + + '' + + '
' + + + '
' + + '' + + + '' + + '
' + + + '
' + + '' + + + '' + + '
' + + + '
' + + '' + + + '' + + '
' + + + '
' + + '' + + + '' + + '
' + + + '' + + '
' + + '
' + + + '' + + '
' + + '
' + + + '
' + + '
' + + + '' + _e("Close") + '' + + '
'; + + // Create the popup + createPopup('favorites', html); + + // Load the favorites + loadFavorites(); + + // Associate the events + launchFavorites(); +} + +// Resets the favorites elements +function resetFavorites() { + var path = '#favorites '; + + $(path + '.wait, ' + path + '.fedit-terminate').hide(); + $(path + '.fedit-add').show(); + $(path + '.fsearch-oneresult').remove(); + $(path + 'input').val(''); + $(path + '.please-complete').removeClass('please-complete'); + $(path + '.fedit-nick').val(getNick()); + $(path + '.fsearch-head-server, ' + path + '.fedit-server').val(HOST_MUC); + $(path + '.fedit-autojoin').attr('checked', false); +} + +// Quits the favorites popup +function quitFavorites() { + // Destroy the popup + destroyPopup('favorites'); + + return false; +} + +// Adds a room to the favorites +function addThisFavorite(roomXID, roomName) { + // Button path + var button = '#favorites .fsearch-results div[data-xid=' + escape(roomXID) + '] a.one-button'; + + // Add a remove button instead of the add one + $(button + '.add').replaceWith('' + _e("Remove") + ''); + + // Click event + $(button + '.remove').click(function() { + return removeThisFavorite(roomXID, roomName); + }); + + // Hide the add button in the (opened?) groupchat + $('#' + hex_md5(roomXID) + ' .tools-add').hide(); + + // Add the database entry + displayFavorites(roomXID, explodeThis(' (', roomName, 0), getNick(), '0', ''); + + // Publish the favorites + favoritePublish(); + + return false; +} + +// Removes a room from the favorites +function removeThisFavorite(roomXID, roomName) { + // Button path + var button = '#favorites .fsearch-results div[data-xid=' + escape(roomXID) + '] a.one-button'; + + // Add a remove button instead of the add one + $(button + '.remove').replaceWith('' + _e("Add") + ''); + + // Click event + $(button + '.add').click(function() { + return addThisFavorite(roomXID, roomName); + }); + + // Show the add button in the (opened?) groupchat + $('#' + hex_md5(roomXID) + ' .tools-add').show(); + + // Remove the favorite + removeFavorite(roomXID, true); + + // Publish the favorites + favoritePublish(); + + return false; +} + +// Edits a favorite +function editFavorite() { + // Path to favorites + var favorites = '#favorites .'; + + // Reset the favorites + resetFavorites(); + + // Show the edit/remove button, hide the others + $(favorites + 'fedit-terminate').hide(); + $(favorites + 'fedit-edit').show(); + $(favorites + 'fedit-remove').show(); + + // We retrieve the values + var xid = $(favorites + 'fedit-head-select').val(); + var data = XMLFromString(getDB('favorites', xid)); + + // If this is not the default room + if(xid != 'none') { + // We apply the values + $(favorites + 'fedit-title').val($(data).find('name').text()); + $(favorites + 'fedit-nick').val($(data).find('nick').text()); + $(favorites + 'fedit-chan').val(getXIDNick(xid)); + $(favorites + 'fedit-server').val(getXIDHost(xid)); + $(favorites + 'fedit-password').val($(data).find('password').text()); + + if($(data).find('autojoin').text() == '1') + $(favorites + 'fedit-autojoin').attr('checked', true); + } + + else + resetFavorites(); +} + +// Adds a favorite +function addFavorite() { + // Path to favorites + var favorites = '#favorites .'; + + // We reset the inputs + $(favorites + 'fedit-title, ' + favorites + 'fedit-nick, ' + favorites + 'fedit-chan, ' + favorites + 'fedit-server, ' + favorites + 'fedit-password').val(''); + + // Show the add button, hide the others + $(favorites + 'fedit-terminate').hide(); + $(favorites + 'fedit-add').show(); +} + +// Terminate a favorite editing +function terminateThisFavorite(type) { + // Path to favorites + var favorites = '#favorites '; + + // We get the values of the current edited groupchat + var old_xid = $(favorites + '.fedit-head-select').val(); + + var title = $(favorites + '.fedit-title').val(); + var nick = $(favorites + '.fedit-nick').val(); + var room = $(favorites + '.fedit-chan').val(); + var server = $(favorites + '.fedit-server').val(); + var xid = room + '@' + server; + var password = $(favorites + '.fedit-password').val(); + var autojoin = '0'; + + if($(favorites + '.fedit-autojoin').filter(':checked').size()) + autojoin = '1'; + + // We check the missing values and send this if okay + if((type == 'add') || (type == 'edit')) { + if(title && nick && room && server) { + // Remove the edited room + if(type == 'edit') + removeFavorite(old_xid, true); + + // Display the favorites + displayFavorites(xid, title, nick, autojoin, password); + + // Reset the inputs + resetFavorites(); + } + + else { + $(favorites + 'input[required]').each(function() { + var select = $(this); + + if(!select.val()) + $(document).oneTime(10, function() { + select.addClass('please-complete').focus(); + }); + else + select.removeClass('please-complete'); + }); + } + } + + // Must remove a favorite? + else if(type == 'remove') { + removeFavorite(old_xid, true); + + // Reset the inputs + resetFavorites(); + } + + // Publish the new favorites + favoritePublish(); + + logThis('Action on this bookmark: ' + room + '@' + server + ' / ' + type, 3); + + return false; +} + +// Removes a favorite +function removeFavorite(xid, database) { + // We remove the target favorite everywhere needed + $('.buddy-conf-groupchat-select option[value=' + xid + ']').remove(); + $('.fedit-head-select option[value=' + xid + ']').remove(); + + // Must remove it from database? + if(database) + removeDB('favorites', xid); +} + +// Sends a favorite to the XMPP server +function favoritePublish() { + var iq = new JSJaCIQ(); + iq.setType('set'); + + var query = iq.setQuery(NS_PRIVATE); + var storage = query.appendChild(iq.buildNode('storage', {'xmlns': NS_BOOKMARKS})); + + // We generate the XML + for(var i = 0; i < sessionStorage.length; i++) { + // Get the pointer values + var current = sessionStorage.key(i); + + // If the pointer is on a stored favorite + if(explodeThis('_', current, 0) == 'favorites') { + var data = XMLFromString(sessionStorage.getItem(current)); + var xid = $(data).find('xid').text(); + var rName = $(data).find('name').text(); + var nick = $(data).find('nick').text(); + var password = $(data).find('password').text(); + var autojoin = $(data).find('autojoin').text(); + + // We create the node for this groupchat + var item = storage.appendChild(iq.buildNode('conference', {'name': rName, 'jid': xid, 'autojoin': autojoin, xmlns: NS_BOOKMARKS})); + item.appendChild(iq.buildNode('nick', {xmlns: NS_BOOKMARKS}, nick)); + + if(password) + item.appendChild(iq.buildNode('password', {xmlns: NS_BOOKMARKS}, password)); + + logThis('Bookmark sent: ' + xid, 3); + } + } + + con.send(iq); +} + +// Gets a list of the MUC items on a given server +function getGCList() { + var path = '#favorites .'; + var gcServer = $('.fsearch-head-server').val(); + + // We reset some things + $(path + 'fsearch-oneresult').remove(); + $(path + 'fsearch-noresults').hide(); + $(path + 'wait').show(); + + var iq = new JSJaCIQ(); + iq.setType('get'); + iq.setTo(gcServer); + + iq.setQuery(NS_DISCO_ITEMS); + + con.send(iq, handleGCList); +} + +// Handles the MUC items list +function handleGCList(iq) { + var path = '#favorites .'; + var from = fullXID(getStanzaFrom(iq)); + + if (!iq || (iq.getType() != 'result')) { + openThisError(3); + + $(path + 'wait').hide(); + + logThis('Error while retrieving the rooms: ' + from, 1); + } + + else { + var handleXML = iq.getQuery(); + + if($(handleXML).find('item').size()) { + // Initialize the HTML code + var html = ''; + + $(handleXML).find('item').each(function() { + var roomXID = $(this).attr('jid'); + var roomName = $(this).attr('name'); + + if(roomXID && roomName) { + // Escaped values + var escaped_xid = encodeOnclick(roomXID); + var escaped_name = encodeOnclick(roomName); + + // Initialize the room HTML + html += '
' + + '
' + roomName.htmlEnc() + '
' + + '' + _e("Join") + ''; + + // This room is yet a favorite + if(existDB('favorites', roomXID)) + html += '' + _e("Remove") + ''; + else + html += '' + _e("Add") + ''; + + // Close the room HTML + html += '
'; + } + }); + + // Append this code to the popup + $(path + 'fsearch-results').append(html); + } + + else + $(path + 'fsearch-noresults').show(); + + logThis('Rooms retrieved: ' + from, 3); + } + + $(path + 'wait').hide(); +} + +// Joins a groupchat from favorites +function joinFavorite(room) { + quitFavorites(); + checkChatCreate(room, 'groupchat', '', '', getXIDNick(room)); + + return false; +} + +// Displays a given favorite +function displayFavorites(xid, name, nick, autojoin, password) { + // Generate the HTML code + var html = ''; + + // Remove the existing favorite + removeFavorite(xid, false); + + // We complete the select forms + $('#buddy-list .gc-join-first-option, #favorites .fedit-head-select-first-option').after(html); + + // We store the informations + var value = '' + xid.htmlEnc() + '' + name.htmlEnc() + '' + nick.htmlEnc() + '' + autojoin.htmlEnc() + '' + password.htmlEnc() + ''; + setDB('favorites', xid, value); +} + +// Loads the favorites for the popup +function loadFavorites() { + // Initialize the HTML code + var html = ''; + + // Read the database + for(var i = 0; i < sessionStorage.length; i++) { + // Get the pointer values + var current = sessionStorage.key(i); + + // If the pointer is on a stored favorite + if(explodeThis('_', current, 0) == 'favorites') { + var data = XMLFromString(sessionStorage.getItem(current)); + + // Add the current favorite to the HTML code + html += ''; + } + } + + // Generate specific HTML code + var favorites_bubble = '' + html; + var favorites_popup = '' + html; + + // Append the HTML code + $('#buddy-list .buddy-conf-groupchat-select').html(favorites_bubble); + $('#favorites .fedit-head-select').html(favorites_popup); +} + +// Plugin launcher +function launchFavorites() { + var path = '#favorites .'; + + // Keyboard events + $(path + 'fsearch-head-server').keyup(function(e) { + if(e.keyCode == 13) { + // No value? + if(!$(this).val()) + $(this).val(HOST_MUC); + + // Get the list + getGCList(); + } + }); + + $(path + 'fedit-line input').keyup(function(e) { + if(e.keyCode == 13) { + // Edit a favorite + if($(path + 'fedit-edit').is(':visible')) + terminateThisFavorite('edit'); + + // Add a favorite + else + terminateThisFavorite('add'); + } + }); + + // Change events + $('.fedit-head-select').change(editFavorite); + + // Click events + $(path + 'room-switcher').click(function() { + $(path + 'favorites-content').hide(); + resetFavorites(); + }); + + $(path + 'room-list').click(function() { + $(path + 'favorites-edit').show(); + }); + + $(path + 'room-search').click(function() { + $(path + 'favorites-search').show(); + getGCList(); + }); + + $(path + 'fedit-add').click(function() { + return terminateThisFavorite('add'); + }); + + $(path + 'fedit-edit').click(function() { + return terminateThisFavorite('edit'); + }); + + $(path + 'fedit-remove').click(function() { + return terminateThisFavorite('remove'); + }); + + $(path + 'bottom .finish').click(function() { + return quitFavorites(); + }); +} diff --git a/jappixmini/jappix/js/features.js b/jappixmini/jappix/js/features.js new file mode 100644 index 00000000..4944be0a --- /dev/null +++ b/jappixmini/jappix/js/features.js @@ -0,0 +1,213 @@ +/* + +Jappix - An open social platform +This is the server features JS script for Jappix + +------------------------------------------------- + +License: AGPL +Author: Vanaryon +Last revision: 01/06/11 + +*/ + +// Gets the features of a server +function getFeatures() { + /* REF: http://xmpp.org/extensions/xep-0030.html */ + + // Get the main values + var to = getServer(); + var caps = con.server_caps; + var xml = null; + + // Try to get the stored data + if(caps) + xml = XMLFromString(getPersistent('caps', caps)); + + // Any stored data? + if(xml) { + handleFeatures(xml); + + logThis('Read server CAPS from cache.'); + } + + // Not stored (or no CAPS)! + else { + var iq = new JSJaCIQ(); + + iq.setTo(to); + iq.setType('get'); + iq.setQuery(NS_DISCO_INFO); + + con.send(iq, handleDiscoInfos); + + logThis('Read server CAPS from network.'); + } +} + +// Handles the features of a server +function handleFeatures(xml) { + // Selector + var selector = $(xml); + + // Markers + var pep = false; + var pubsub = false; + var archive = false; + var archive_auto = false; + var archive_manual = false; + var archive_manage = false; + var archive_pref = false; + var commands = false; + + // Scan the features + if(selector.find('identity[category=pubsub][type=pep]').size()) + pep = true; + if(selector.find('feature[var=' + NS_PUBSUB + ']').size()) + pubsub = true; + if(selector.find('feature[var=' + NS_URN_ARCHIVE + ']').size()) + archive = true; + if(selector.find('feature[var=' + NS_URN_AR_AUTO + ']').size()) + archive_auto = true; + if(selector.find('feature[var=' + NS_URN_AR_MANUAL + ']').size()) + archive_manual = true; + if(selector.find('feature[var=' + NS_URN_AR_MANAGE + ']').size()) + archive_manage = true; + if(selector.find('feature[var=' + NS_URN_AR_PREF + ']').size()) + archive_pref = true; + if(selector.find('feature[var=' + NS_COMMANDS + ']').size()) + commands = true; + + // Enable the pep elements if available + if(pep) { + // Update our database + enableFeature('pep'); + + // Get the microblog + getInitMicroblog(); + + // Get the notifications + getNotifications(); + + // Geolocate the user + geolocate(); + + // Enable microblogging send tools + waitMicroblog('sync'); + $('.postit.attach').css('display', 'block'); + + logThis('XMPP server supports PEP.', 3); + } + + // Disable microblogging send tools (no PEP!) + else { + waitMicroblog('unsync'); + + logThis('XMPP server does not support PEP.', 2); + } + + // Enable the pubsub features if available + if(pubsub) + enableFeature(NS_PUBSUB); + + // Enable the archiving features if available + if(archive) + enableFeature(NS_URN_ARCHIVE); + + // Enable the archiving sub-features if available + if(archive_pref) + enableFeature(NS_URN_AR_PREF); + if(archive_auto) + enableFeature(NS_URN_AR_AUTO); + if(archive_manual) + enableFeature(NS_URN_AR_MANUAL); + if(archive_manage) + enableFeature(NS_URN_AR_MANAGE); + + // Enable the commands features if available + if(commands) + enableFeature(NS_COMMANDS); + + // Hide the private life fieldset if nothing to show + if(!pep && !archive_pref) + $('#options fieldset.privacy').hide(); + + // Apply the features + applyFeatures('talk'); + + // Process the buddy-list height + if(pep) + adaptRoster(); + + return false; +} + +// The function to apply the features to an element +function applyFeatures(id) { + // Path to the elements + var path = '#' + id + ' .'; + + // PEP features + if(enabledPEP()) + $(path + 'pep-hidable').show(); + + // PubSub features + if(enabledPubSub()) + $(path + 'pubsub-hidable').show(); + + // Archives features + if(enabledArchives() || enabledArchives('auto') || enabledArchives('manual') || enabledArchives('manage')) { + $(path + 'archives-hidable:not(.pref)').show(); + + // Sub-feature: archives preferences + if(enabledArchives('pref')) + $(path + 'archives-hidable.pref').show(); + } + + // Commands features + if(enabledCommands()) + $(path + 'commands-hidable').show(); + + // XMPP links (browser feature) + if(navigator.registerProtocolHandler) + $(path + 'xmpplinks-hidable').show(); +} + +// Enables a feature +function enableFeature(feature) { + setDB('feature', feature, 'true'); +} + +// Checks if a feature is enabled +function enabledFeature(feature) { + if(getDB('feature', feature) == 'true') + return true; + else + return false; +} + +// Returns the XMPP server PEP support +function enabledPEP() { + return enabledFeature('pep'); +} + +// Returns the XMPP server PubSub support +function enabledPubSub() { + return enabledFeature(NS_PUBSUB); +} + +// Returns the XMPP server archives support +function enabledArchives(sub) { + var xmlns = NS_URN_ARCHIVE; + + // Any sub element sent? + if(sub) + xmlns += ':' + sub; + + return enabledFeature(xmlns); +} + +// Returns the XMPP server commands support +function enabledCommands() { + return enabledFeature(NS_COMMANDS); +} diff --git a/jappixmini/jappix/js/filter.js b/jappixmini/jappix/js/filter.js new file mode 100644 index 00000000..48e5238f --- /dev/null +++ b/jappixmini/jappix/js/filter.js @@ -0,0 +1,189 @@ +/* + +Jappix - An open social platform +These are the filtering JS script for Jappix + +------------------------------------------------- + +License: AGPL +Authors: Vanaryon, Maranda +Last revision: 04/08/11 + +*/ + +// Generates a given emoticon HTML code +function emoteImage(image, text, after) { + return ' ' + encodeQuotes(text) + ' ' + after; +} + +// Filters a given message +function filterThisMessage(neutralMessage, nick, html_encode) { + var filteredMessage = neutralMessage; + + // We encode the HTML special chars + if(html_encode) + filteredMessage = filteredMessage.htmlEnc(); + + // /me command + filteredMessage = filteredMessage.replace(/((^)|((.+)(>)))(\/me )([^<]+)/, nick + ' $7') + + // We replace the smilies text into images + .replace(/(:-?@)($|\s|<)/gi, emoteImage('angry', '$1', '$2')) + .replace(/(:-?\[)($|\s|<)/gi, emoteImage('bat', '$1', '$2')) + .replace(/(\(B\))($|\s|<)/g, emoteImage('beer', '$1', '$2')) + .replace(/((:-?D)|(XD))($|\s|<)/gi, emoteImage('biggrin', '$1', '$4')) + .replace(/(:-?\$)($|\s|<)/gi, emoteImage('blush', '$1', '$2')) + .replace(/(\(Z\))($|\s|<)/g, emoteImage('boy', '$1', '$2')) + .replace(/(\(W\))($|\s|<)/g, emoteImage('brflower', '$1', '$2')) + .replace(/((<\/3)|(\(U\)))($|\s|<)/g, emoteImage('brheart', '$1', '$4')) + .replace(/(\(C\))($|\s|<)/g, emoteImage('coffee', '$1', '$2')) + .replace(/((8-\))|(\(H\)))($|\s|<)/g, emoteImage('coolglasses', '$1', '$4')) + .replace(/(:'-?\()($|\s|<)/gi, emoteImage('cry', '$1', '$2')) + .replace(/(\(%\))($|\s|<)/g, emoteImage('cuffs', '$1', '$2')) + .replace(/(\]:-?>)($|\s|<)/gi, emoteImage('devil', '$1', '$2')) + .replace(/(\(D\))($|\s|<)/g, emoteImage('drink', '$1', '$2')) + .replace(/(@}->--)($|\s|<)/gi, emoteImage('flower', '$1', '$2')) + .replace(/((:-?\/)|(:-?S))($|\s|<)/gi, emoteImage('frowning', '$1', '$4')) + .replace(/(\(X\))($|\s|<)/g, emoteImage('girl', '$1', '$2')) + .replace(/((<3)|(\(L\)))($|\s|<)/g, emoteImage('heart', '$1', '$4')) + .replace(/(\(}\))($|\s|<)/g, emoteImage('hugleft', '$1', '$2')) + .replace(/(\({\))($|\s|<)/g, emoteImage('hugright', '$1', '$2')) + .replace(/(:-?{})($|\s|<)/gi, emoteImage('kiss', '$1', '$2')) + .replace(/(\(I\))($|\s|<)/g, emoteImage('lamp', '$1', '$2')) + .replace(/(:-?3)($|\s|<)/gi, emoteImage('lion', '$1', '$2')) + .replace(/(\(E\))($|\s|<)/g, emoteImage('mail', '$1', '$2')) + .replace(/(\(S\))($|\s|<)/g, emoteImage('moon', '$1', '$2')) + .replace(/(\(8\))($|\s|<)/g, emoteImage('music', '$1', '$2')) + .replace(/((=-?O)|(:-?O))($|\s|<)/gi, emoteImage('oh', '$1', '$4')) + .replace(/(\(T\))($|\s|<)/g, emoteImage('phone', '$1', '$2')) + .replace(/(\(P\))($|\s|<)/g, emoteImage('photo', '$1', '$2')) + .replace(/(:-?!)($|\s|<)/gi, emoteImage('puke', '$1', '$2')) + .replace(/(\(@\))($|\s|<)/g, emoteImage('pussy', '$1', '$2')) + .replace(/(\(R\))($|\s|<)/g, emoteImage('rainbow', '$1', '$2')) + .replace(/(:-?\))($|\s|<)/gi, emoteImage('smile', '$1', '$2')) + .replace(/(\(\*\))($|\s|<)/g, emoteImage('star', '$1', '$2')) + .replace(/(:-?\|)($|\s|<)/gi, emoteImage('stare', '$1', '$2')) + .replace(/(\(N\))($|\s|<)/g, emoteImage('thumbdown', '$1', '$2')) + .replace(/(\(Y\))($|\s|<)/g, emoteImage('thumbup', '$1', '$2')) + .replace(/(:-?P)($|\s|<)/gi, emoteImage('tongue', '$1', '$2')) + .replace(/(:-?\()($|\s|<)/gi, emoteImage('unhappy', '$1', '$2')) + .replace(/(;-?\))($|\s|<)/gi, emoteImage('wink', '$1', '$2')) + + // Text in bold + .replace(/(^|\s|>)((\*)([^<>'"]+)(\*))($|\s|<)/gi, '$1$2$6') + + // Italic text + .replace(/(^|\s|>)((\/)([^<>'"]+)(\/))($|\s|<)/gi, '$1$2$6') + + // Underlined text + .replace(/(^|\s|>)((_)([^<>'"]+)(_))($|\s|<)/gi, '$1$2$6'); + + // Add the links + if(html_encode) + filteredMessage = applyLinks(filteredMessage, 'desktop'); + + // Filter integratebox links + filteredMessage = filterIntegrateBox(filteredMessage); + + return filteredMessage; +} + +// Filters a xHTML message to be displayed in Jappix +function filterThisXHTML(code) { + // Allowed elements array + var elements = new Array( + 'a', + 'abbr', + 'acronym', + 'address', + 'blockquote', + 'body', + 'br', + 'cite', + 'code', + 'dd', + 'dfn', + 'div', + 'dt', + 'em', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'head', + 'html', + 'img', + 'kbd', + 'li', + 'ol', + 'p', + 'pre', + 'q', + 'samp', + 'span', + 'strong', + 'title', + 'ul', + 'var' + ); + + // Allowed attributes array + var attributes = new Array( + 'accesskey', + 'alt', + 'charset', + 'cite', + 'class', + 'height', + 'href', + 'hreflang', + 'id', + 'longdesc', + 'profile', + 'rel', + 'rev', + 'src', + 'style', + 'tabindex', + 'title', + 'type', + 'uri', + 'version', + 'width', + 'xml:lang', + 'xmlns' + ); + + // Remove forbidden elements + $(code).find('html body *').each(function() { + // This element is not authorized + if(!existArrayValue(elements, (this).nodeName.toLowerCase())) + $(this).remove(); + }); + + // Remove forbidden attributes + $(code).find('html body *').each(function() { + // Put a pointer on this element (jQuery way & normal way) + var cSelector = $(this); + var cElement = (this); + + // Loop the attributes of the current element + $(cElement.attributes).each(function(index) { + // Read the current attribute + var cAttr = cElement.attributes[index]; + var cName = cAttr.name; + var cVal = cAttr.value; + + // This attribute is not authorized, or contains JS code + if(!existArrayValue(attributes, cName.toLowerCase()) || ((cVal.toLowerCase()).match(/(^|"|')javascript:/))) + cSelector.removeAttr(cName); + }); + }); + + // Filter some other elements + $(code).find('a').attr('target', '_blank'); + + return $(code).find('html body').html(); +} diff --git a/jappixmini/jappix/js/groupchat.js b/jappixmini/jappix/js/groupchat.js new file mode 100644 index 00000000..c8dd13b1 --- /dev/null +++ b/jappixmini/jappix/js/groupchat.js @@ -0,0 +1,283 @@ +/* + +Jappix - An open social platform +These are the groupchat JS scripts for Jappix + +------------------------------------------------- + +License: AGPL +Authors: Vanaryon, Maranda, Eric +Last revision: 28/08/11 + +*/ + +// Displays the MUC admin elements +function displayMucAdmin(affiliation, id, xid, statuscode) { + // We must be in the "login" mode + if(isAnonymous()) + return; + + // We check if the user is a room owner or administrator to give him privileges + if(affiliation == 'owner' || affiliation == 'admin') + $('#' + id + ' .tools-mucadmin').show(); + + // We check if the room hasn't been yet created + if(statuscode == 201) + openThisInfo(4); + + // We add the click event + $('#' + id + ' .tools-mucadmin').click(function() { + openMucAdmin(xid, affiliation); + }); +} + +// Initializes a connection with a MUC groupchat +function getMUC(room, nickname, password) { + // Room hash + var hash = hex_md5(room); + + // Reset the elements + $('#' + hash + ' .muc-ask').remove(); + $('#' + hash + ' .compose').show(); + + // No nickname? + if(!nickname) { + // Get some values + if(!isAnonymous()) + nickname = getNick(); + else + nickname = ANONYMOUS_NICK; + + // If the nickname could not be retrieved, ask it + if(!nickname) + generateMUCAsk('nickname', room, hash, nickname, password); + } + + // Got our nickname? + if(nickname) { + // Get our general presence + var show = getDB('presence-show', 1); + var status = getDB('options', 'presence-status'); + + // Set my nick + $('#' + hash).attr('data-nick', escape(nickname)); + + // Send the appropriate presence + sendPresence(room + '/' + nickname, '', show, status, '', true, password, handleMUC); + } + + return false; +} + +// Handles the MUC main elements +function handleMUC(presence) { + // We get the xml content + var xml = presence.getNode(); + var from = fullXID(getStanzaFrom(presence)); + var room = bareXID(from); + var nickname = thisResource(from); + var hash = hex_md5(room); + + // No ID: must fix M-Link bug + if(presence.getID() == null) + presence.setID(1); + + logThis('First MUC presence: ' + from, 3); + + // Catch the errors + if(!handleError(xml)) { + // Define some stuffs + var muc_user = $(xml).find('x[xmlns=' + NS_MUC_USER + ']'); + var affiliation = muc_user.find('item').attr('affiliation'); + var statuscode = parseInt(muc_user.find('status').attr('code')); + + // Handle my presence + handlePresence(presence); + + // Check if I am a room owner + displayMucAdmin(affiliation, hash, room, statuscode); + + // Tell the MUC we can notify the incoming presences + $(document).oneTime('15s', function() { + $('#' + hash).attr('data-initial', 'true'); + }); + + // Enable the chatting input + $(document).oneTime(10, function() { + $('#' + hash + ' .message-area').removeAttr('disabled').focus(); + }); + } + + // A password is required + else if($(xml).find('error[type=auth] not-authorized').size()) + generateMUCAsk('password', room, hash, nickname); + + // There's a nickname conflict + else if($(xml).find('error[type=cancel] conflict').size()) + generateMUCAsk('nickname', room, hash); +} + +// Generates a correct MUC asker +function generateMUCAsk(type, room, hash, nickname, password) { + // Generate the path to the elements + var path_to = '#' + hash + ' .muc-ask'; + + // Define the label text + var label_text; + + switch(type) { + case 'nickname': + label_text = _e("Nickname"); + break; + + case 'password': + label_text = _e("Password"); + break; + } + + // Create the HTML markup + $('#' + hash + ' .compose').hide(); + + $('#' + hash).append( + '
' + + '' + + '' + + '
' + ); + + // When a key is pressed in the input + $(path_to + ' input').keyup(function(e) { + var value_input = $(this).val(); + + // Enter key pressed + if((e.keyCode == 13) && value_input) { + if(type == 'nickname') + nickname = value_input; + else if(type == 'password') + password = value_input; + + return getMUC(room, nickname, password); + } + }); + + // Focus on the input + $(document).oneTime(10, function() { + $(path_to + ' input').focus(); + }); +} + +// Creates a new groupchat +function groupchatCreate(hash, room, chan, nickname, password) { + /* REF: http://xmpp.org/extensions/xep-0045.html */ + + logThis('New groupchat: ' + room, 3); + + // Create the chat content + generateChat('groupchat', hash, room, chan); + + // Create the chat switcher + generateSwitch('groupchat', hash, room, chan); + + // The icons-hover functions + tooltipIcons(room, hash); + + // Click event on the add tool + $('#' + hash + ' .tools-add').click(function() { + // Hide the icon (to tell the user all is okay) + $(this).hide(); + + // Add the groupchat to the user favorites + addThisFavorite(room, chan); + }); + + // Must show the add button? + if(!existDB('favorites', room)) + $('#' + hash + ' .tools-add').show(); + + // The event handlers + var inputDetect = $('#' + hash + ' .message-area'); + + // Focus event + inputDetect.focus(function() { + chanCleanNotify(hash); + }) + + // Blur event + inputDetect.blur(function() { + resetAutocompletion(hash); + }) + + // Lock to the input + inputDetect.keypress(function(e) { + // Enter key + if(e.keyCode == 13) { + // Add a new line + if(e.shiftKey) + inputDetect.val(inputDetect.val() + '\n'); + + // Send the message + else { + sendMessage(hash, 'groupchat'); + + // Reset the composing database entry + setDB('chatstate', room, 'off'); + } + + return false; + } + + // Tabulation key + else if(e.keyCode == 9) { + createAutocompletion(hash); + + return false; + } + + // Reset the autocompleter + else + resetAutocompletion(hash); + }); + + // Chatstate events + eventsChatState(inputDetect, room, hash); + + // Get the current muc informations and content + getMUC(room, nickname, password); +} + +// Joins the defined groupchats +function joinConfGroupchats() { + // Nothing to join? + if(!GROUPCHATS_JOIN) + return; + + // Values array + var muc_arr = [GROUPCHATS_JOIN]; + var new_arr = []; + + // Try to split it + if(GROUPCHATS_JOIN.indexOf(',') != -1) + muc_arr = GROUPCHATS_JOIN.split(','); + + for(i in muc_arr) { + // Get the current value + var muc_current = trim(muc_arr[i]); + + // No current value? + if(!muc_current) + continue; + + // Filter the current value + muc_current = generateXID(muc_current, 'groupchat'); + + // Add the current value + if(!existArrayValue(new_arr, muc_current)) + new_arr.push(muc_current); + } + + // Join the chats + if(new_arr.length) { + for(g in new_arr) + checkChatCreate(new_arr[g], 'groupchat'); + } +} diff --git a/jappixmini/jappix/js/home.js b/jappixmini/jappix/js/home.js new file mode 100644 index 00000000..5322501d --- /dev/null +++ b/jappixmini/jappix/js/home.js @@ -0,0 +1,371 @@ +/* + +Jappix - An open social platform +These are the homepage JS scripts for Jappix + +------------------------------------------------- + +License: AGPL +Authors: Vanaryon, LinkMauve +Last revision: 15/01/12 + +*/ + +// Allows the user to switch the difference home page elements +function switchHome(div) { + // Path to + var home = '#home .'; + var right = home + 'right '; + var current = right + '.homediv.' + div; + + // We switch the div + $(right + '.homediv, ' + right + '.top').hide(); + $(right + '.' + div).show(); + + // We reset the homedivs + $(home + 'homediv:not(.default), ' + home + 'top:not(.default)').remove(); + + // Get the HTML code to display + var disable_form = ''; + var lock_host = ''; + var code = ''; + + // Apply the previous link + switch(div) { + case 'loginer': + case 'anonymouser': + case 'registerer': + if(!exists(right + '.top.sub')) { + // Append the HTML code for previous link + $(right + '.top.default').after('

«

'); + + // Click event on previous link + $(home + 'top.sub a.previous').click(function() { + return switchHome('default'); + }); + } + + break; + } + + // Apply the form + switch(div) { + // Login tool + case 'loginer': + lock_host = disableInput(LOCK_HOST, 'on'); + code = '

' + printf(_e("Login to your existing XMPP account. You can also use the %s to join a groupchat."), '' + _e("anonymous mode") + '') + '

' + + + '' + + '
' + + '' + _e("Required") + '' + + + '' + + '@' + + '' + + '' + + '' + + '' + + '
' + + + '' + _e("Advanced") + '' + + + '
' + + '' + _e("Advanced") + '' + + + '' + + '' + + '' + + '' + + '
' + + + '' + + ''; + + break; + + // Anonymous login tool + case 'anonymouser': + disable_form = disableInput(ANONYMOUS, 'off'); + code = '

' + printf(_e("Enter the groupchat you want to join and the nick you want to have. You can also go back to the %s."), '' + _e("login page") + '') + '

' + + + '
' + + '
' + + '' + _e("Required") + '' + + + '' + + '' + + + '' + + '' + + '
' + + + '' + + '
' + + + '
' + + _e("Share this link with your friends:") + ' ' + + '
'; + + break; + + // Register tool + case 'registerer': + disable_form = disableInput(REGISTRATION, 'off'); + + if(!disable_form) + lock_host = disableInput(LOCK_HOST, 'on'); + + code = '

' + _e("Register a new XMPP account to join your friends on your own social cloud. That's simple!") + '

' + + + '
' + + '
' + + '' + _e("Required") + '' + + + '' + + '@' + + '' + + '' + + '' + + '
' + + + '' + + '
'; + + break; + } + + // Form disabled? + if(disable_form) + code += '
' + + _e("This tool has been disabled, you cannot use it!") + + '
'; + + // Create this HTML code + if(code && !exists(current)) { + // Append it! + $(right + '.homediv.default').after('
' + code + '
'); + + // Create the attached events + switch(div) { + // Login tool + case 'loginer': + $(current + ' a.to-anonymous').click(function() { + return switchHome('anonymouser'); + }); + + $(current + ' a.advanced').click(showAdvanced); + + $(current + ' form').submit(loginForm); + + break; + + // Anonymous login tool + case 'anonymouser': + $(current + ' a.to-home').click(function() { + return switchHome('loginer'); + }); + + $(current + ' form').submit(doAnonymous); + + // Keyup event on anonymous join's room input + $(current + ' input.room').keyup(function() { + var value = $(this).val(); + var report = current + ' .report'; + var span = report + ' span'; + + if(!value) { + $(report).hide(); + $(span).text(''); + } + + else { + $(report).show(); + $(span).text(JAPPIX_LOCATION + '?r=' + value); + } + }); + + break; + + // Register tool + case 'registerer': + $(current + ' form').submit(registerForm); + + break; + } + } + + // We focus on the first input + $(document).oneTime(10, function() { + $(right + 'input:visible:first').focus(); + }); + + return false; +} + +// Allows the user to display the advanced login options +function showAdvanced() { + // Hide the link + $('#home a.advanced').hide(); + + // Show the fieldset + $('#home fieldset.advanced').show(); + + return false; +} + +// Reads the login form values +function loginForm() { + // We get the values + var lPath = '#home .loginer '; + var lServer = $(lPath + '.server').val(); + var lNick = $(lPath + '.nick').val(); + var lPass = $(lPath + '.password').val(); + var lResource = $(lPath + '.resource').val(); + var lPriority = $(lPath + '.priority').val(); + var lRemember = $(lPath + '.remember').filter(':checked').size(); + + // Enough values? + if(lServer && lNick && lPass && lResource && lPriority) + doLogin(lNick, lServer, lPass, lResource, lPriority, lRemember); + + // Something is missing? + else { + $(lPath + 'input[type=text], ' + lPath + 'input[type=password]').each(function() { + var select = $(this); + + if(!select.val()) + $(document).oneTime(10, function() { + select.addClass('please-complete').focus(); + }); + else + select.removeClass('please-complete'); + }); + } + + return false; +} + +// Reads the register form values +function registerForm() { + var rPath = '#home .registerer '; + + // Remove the success info + $(rPath + '.success').remove(); + + // Get the values + var username = $(rPath + '.nick').val(); + var domain = $(rPath + '.server').val(); + var pass = $(rPath + '.password').val(); + var spass = $(rPath + '.spassword').val(); + + // Enough values? + if(domain && username && pass && spass && (pass == spass)) { + // We remove the not completed class to avoid problems + $('#home .registerer input').removeClass('please-complete'); + + // Fire the register event! + doRegister(username, domain, pass); + } + + // Something is missing? + else { + $(rPath + 'input[type=text], ' + rPath + 'input[type=password]').each(function() { + var select = $(this); + + if(!select.val() || (select.is('#spassword') && pass && (pass != spass))) + $(document).oneTime(10, function() { + select.addClass('please-complete').focus(); + }); + else + select.removeClass('please-complete'); + }); + } + + return false; +} + +// Plugin launcher +function launchHome() { + // Define the vars + var home = '#home '; + var button = home + 'button'; + var corp = home + '.corporation'; + var locale = home + '.locale'; + + // Removes the