diff --git a/boot.php b/boot.php index 4bdf25d410..f2cc34aea3 100644 --- a/boot.php +++ b/boot.php @@ -11,7 +11,7 @@ require_once('include/cache.php'); require_once('library/Mobile_Detect/Mobile_Detect.php'); define ( 'FRIENDICA_PLATFORM', 'Friendica'); -define ( 'FRIENDICA_VERSION', '3.0.1518' ); +define ( 'FRIENDICA_VERSION', '3.0.1523' ); define ( 'DFRN_PROTOCOL_VERSION', '2.23' ); define ( 'DB_UPDATE_VERSION', 1156 ); @@ -361,17 +361,26 @@ if(! class_exists('App')) { // Allow themes to control internal parameters // by changing App values in theme.php - // - // Possibly should make these part of the plugin - // system, but it seems like overkill to invoke - // all the plugin machinery just to change a couple - // of values + public $sourcename = ''; public $videowidth = 425; public $videoheight = 350; public $force_max_items = 0; public $theme_thread_allow = true; + // An array for all theme-controllable parameters + // Mostly unimplemented yet. Only options 'stylesheet' and + // beyond are used. + + public $theme = array( + 'sourcename' => '', + 'videowidth' => 425, + 'videoheight' => 350, + 'force_max_items' => 0, + 'thread_allow' => true, + 'stylesheet' => '' + ); + private $scheme; private $hostname; private $baseurl; @@ -580,6 +589,13 @@ if(! class_exists('App')) { $interval = 40000; $this->page['title'] = $this->config['sitename']; + + /* put the head template at the beginning of page['htmlhead'] + * since the code added by the modules frequently depends on it + * being first + */ + if(!isset($this->page['htmlhead'])) + $this->page['htmlhead'] = ''; $tpl = get_markup_template('head.tpl'); $this->page['htmlhead'] = replace_macros($tpl,array( '$baseurl' => $this->get_baseurl(), // FIXME for z_path!!!! @@ -590,14 +606,16 @@ if(! class_exists('App')) { '$showmore' => t('show more'), '$showfewer' => t('show fewer'), '$update_interval' => $interval - )); + )) . $this->page['htmlhead']; } function init_page_end() { + if(!isset($this->page['end'])) + $this->page['end'] = ''; $tpl = get_markup_template('end.tpl'); $this->page['end'] = replace_macros($tpl,array( '$baseurl' => $this->get_baseurl() // FIXME for z_path!!!! - )); + )) . $this->page['end']; } function set_curl_code($code) { @@ -917,6 +935,7 @@ if(! function_exists('login')) { $tpl = get_markup_template("login.tpl"); $_SESSION['return_url'] = $a->query_string; + $a->module = 'login'; } @@ -928,6 +947,7 @@ if(! function_exists('login')) { '$lname' => array('username', t('Nickname or Email address: ') , '', ''), '$lpassword' => array('password', t('Password: '), '', ''), + '$lremember' => array('remember', t('Remember me'), 0, ''), '$openid' => !$noid, '$lopenid' => array('openid_url', t('Or login using OpenID: '),'',''), @@ -989,6 +1009,13 @@ if(! function_exists('remote_user')) { // a page is loaded. Usually used for errors or alerts. if(! function_exists('notice')) { + /** + * Show an error message to user. + * + * This function save text in session, to be shown to the user at next page load + * + * @param string $s - Text of notice + */ function notice($s) { $a = get_app(); if(! x($_SESSION,'sysmsg')) $_SESSION['sysmsg'] = array(); @@ -997,6 +1024,13 @@ if(! function_exists('notice')) { } } if(! function_exists('info')) { + /** + * Show an info message to user. + * + * This function save text in session, to be shown to the user at next page load + * + * @param string $s - Text of notice + */ function info($s) { $a = get_app(); if(! x($_SESSION,'sysmsg_info')) $_SESSION['sysmsg_info'] = array(); diff --git a/doc/Account-Basics.md b/doc/Account-Basics.md index 41ca95b360..2c3c05e8bf 100644 --- a/doc/Account-Basics.md +++ b/doc/Account-Basics.md @@ -73,5 +73,7 @@ You can export a copy of your personal data in XML format from the "Export perso * [Groups and Privacy](help/Groups-and-Privacy) +* [Move Account](help/Move-Account) + * [Remove Account](help/Remove-Account) diff --git a/doc/Home.md b/doc/Home.md index 1df74b5f26..7c86ce805a 100644 --- a/doc/Home.md +++ b/doc/Home.md @@ -14,6 +14,7 @@ Friendica Documentation and Resources * [Groups and Privacy](help/Groups-and-Privacy) * [Tags and Mentions](help/Tags-and-Mentions) * [Pages](help/Pages) +* [Move Account](help/Move-Account) * [Remove Account](help/Remove-Account) * [Bugs and Issues](help/Bugs-and-Issues) diff --git a/doc/Move-Account.md b/doc/Move-Account.md new file mode 100644 index 0000000000..cb20f99b42 --- /dev/null +++ b/doc/Move-Account.md @@ -0,0 +1,33 @@ +Move Account +============ + +* [Home](help) + + +! **this is an experimental feature** + +** How to move an account between servers ** + +Go to "Settings" -> "[Export personal data](uexport)" +Click on "Export account" to save your account data. +This file contains your details, your contacts, groups, and personal settings. +It contains also your secret keys to authenticate yourself to your contacts: +**save this file in a secure place**! + +Go to your new server, and open *http://newserver.com/uimport* (there is not a +direct link to this page at the moment). + +Load your saved account file and click "Import". + +Friendica will recreate your account on new server, with your contacts and groups. +A message is sent to Friendica contacts, to inform them about your move: if your +contacts are runnning on an updated server, automatically your details on their +side will be updated. +Contacts on Statusnet/Identi.ca or Diaspora will be archived, as we can't inform +them about your move. +You should ask them to remove your contact from their lists and readd you, and you +should do the same with their contact. + +After the move, the account on the old server will not work reliably anymore, and +should be not used. + diff --git a/include/Scrape.php b/include/Scrape.php index 7eaac3b449..611cbda886 100644 --- a/include/Scrape.php +++ b/include/Scrape.php @@ -590,6 +590,12 @@ function probe_url($url, $mode = PROBE_NORMAL) { $check_feed = false; + if(stristr($url,'tumblr.com') && (! stristr($url,'/rss'))) { + $poll = $url . '/rss'; + $check_feed = true; + // Will leave it to others to figure out how to grab the avatar, which is on the $url page in the open graph meta links + } + if($twitter || ! $poll) $check_feed = true; if((! isset($vcard)) || (! x($vcard,'fn')) || (! $profile)) diff --git a/include/auth.php b/include/auth.php index 523de88ce9..b534d4a4d3 100644 --- a/include/auth.php +++ b/include/auth.php @@ -2,6 +2,7 @@ require_once('include/security.php'); +require_once('include/datetime.php'); function nuke_session() { unset($_SESSION['authenticated']); @@ -68,7 +69,18 @@ if((isset($_SESSION)) && (x($_SESSION,'authenticated')) && ((! (x($_POST,'auth-p goaway(z_root()); } - authenticate_success($r[0]); + // Make sure to refresh the last login time for the user if the user + // stays logged in for a long time, e.g. with "Remember Me" + $login_refresh = false; + if(! x($_SESSION['last_login_date'])) { + $_SESSION['last_login_date'] = datetime_convert('UTC','UTC'); + } + if( strcmp(datetime_convert('UTC','UTC','now - 12 hours'), $_SESSION['last_login_date']) > 0 ) { + + $_SESSION['last_login_date'] = datetime_convert('UTC','UTC'); + $login_refresh = true; + } + authenticate_success($r[0], false, false, $login_refresh); } } else { @@ -162,8 +174,36 @@ else { goaway(z_root()); } + // If the user specified to remember the authentication, then change the cookie + // to expire after one year (the default is when the browser is closed). + // If the user did not specify to remember, change the cookie to expire when the + // browser is closed. The reason this is necessary is because if the user + // specifies to remember, then logs out and logs back in without specifying to + // remember, the old "remember" cookie may remain and prevent the session from + // expiring when the browser is closed. + // + // It seems like I should be able to test for the old cookie, but for some reason when + // I read the lifetime value from session_get_cookie_params(), I always get '0' + // (i.e. expire when the browser is closed), even when there's a time expiration + // on the cookie + if($_POST['remember']) { + $old_sid = session_id(); + session_set_cookie_params('31449600'); // one year + session_regenerate_id(false); + + q("UPDATE session SET sid = '%s' WHERE sid = '%s'", dbesc(session_id()), dbesc($old_sid)); + } + else { + $old_sid = session_id(); + session_set_cookie_params('0'); + session_regenerate_id(false); + + q("UPDATE session SET sid = '%s' WHERE sid = '%s'", dbesc(session_id()), dbesc($old_sid)); + } + // if we haven't failed up this point, log them in. + $_SESSION['last_login_date'] = datetime_convert('UTC','UTC'); authenticate_success($record, true, true); } } diff --git a/include/conversation.php b/include/conversation.php index 43d20a4014..1de77feb1b 100644 --- a/include/conversation.php +++ b/include/conversation.php @@ -865,11 +865,15 @@ function format_like($cnt,$arr,$type,$id) { $total = count($arr); if($total >= MAX_LIKERS) $arr = array_slice($arr, 0, MAX_LIKERS - 1); - if($total < MAX_LIKERS) - $arr[count($arr)-1] = t('and') . ' ' . $arr[count($arr)-1]; - $str = implode(', ', $arr); - if($total >= MAX_LIKERS) + if($total < MAX_LIKERS) { + $last = t('and') . ' ' . $arr[count($arr)-1]; + $arr2 = array_slice($arr, 0, -1); + $str = implode(', ', $arr2) . ' ' . $last; + } + if($total >= MAX_LIKERS) { + $str = implode(', ', $arr); $str .= sprintf( t(', and %d other people'), $total - MAX_LIKERS ); + } $str = (($type === 'like') ? sprintf( t('%s like this.'), $str) : sprintf( t('%s don\'t like this.'), $str)); $o .= "\t" . '
'; } diff --git a/include/onepoll.php b/include/onepoll.php index 019455e820..a50d234e22 100644 --- a/include/onepoll.php +++ b/include/onepoll.php @@ -294,34 +294,147 @@ function onepoll_run(&$argv, &$argc){ $metas = email_msg_meta($mbox,implode(',',$msgs)); if(count($metas) != count($msgs)) { logger("onepoll: for " . $mailconf[0]['user'] . " there are ". count($msgs) . " messages but received " . count($metas) . " metas", LOGGER_DEBUG); - break; } - $msgs = array_combine($msgs, $metas); + else { + $msgs = array_combine($msgs, $metas); - foreach($msgs as $msg_uid => $meta) { - logger("Mail: Parsing mail ".$msg_uid, LOGGER_DATA); + foreach($msgs as $msg_uid => $meta) { + logger("Mail: Parsing mail ".$msg_uid, LOGGER_DATA); - $datarray = array(); -// $meta = email_msg_meta($mbox,$msg_uid); -// $headers = email_msg_headers($mbox,$msg_uid); + $datarray = array(); + // $meta = email_msg_meta($mbox,$msg_uid); + // $headers = email_msg_headers($mbox,$msg_uid); - $datarray['uri'] = msgid2iri(trim($meta->message_id,'<>')); + $datarray['uri'] = msgid2iri(trim($meta->message_id,'<>')); - // Have we seen it before? - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `uri` = '%s' LIMIT 1", - intval($importer_uid), - dbesc($datarray['uri']) - ); + // Have we seen it before? + $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `uri` = '%s' LIMIT 1", + intval($importer_uid), + dbesc($datarray['uri']) + ); - if(count($r)) { - logger("Mail: Seen before ".$msg_uid." for ".$mailconf[0]['user'],LOGGER_DEBUG); - if($meta->deleted && ! $r[0]['deleted']) { - q("UPDATE `item` SET `deleted` = 1, `changed` = '%s' WHERE `id` = %d LIMIT 1", - dbesc(datetime_convert()), - intval($r[0]['id']) - ); + if(count($r)) { + logger("Mail: Seen before ".$msg_uid." for ".$mailconf[0]['user'],LOGGER_DEBUG); + if($meta->deleted && ! $r[0]['deleted']) { + q("UPDATE `item` SET `deleted` = 1, `changed` = '%s' WHERE `id` = %d LIMIT 1", + dbesc(datetime_convert()), + intval($r[0]['id']) + ); + } + /*switch ($mailconf[0]['action']) { + case 0: + logger("Mail: Seen before ".$msg_uid." for ".$mailconf[0]['user'].". Doing nothing.", LOGGER_DEBUG); + break; + case 1: + logger("Mail: Deleting ".$msg_uid." for ".$mailconf[0]['user']); + imap_delete($mbox, $msg_uid, FT_UID); + break; + case 2: + logger("Mail: Mark as seen ".$msg_uid." for ".$mailconf[0]['user']); + imap_setflag_full($mbox, $msg_uid, "\\Seen", ST_UID); + break; + case 3: + logger("Mail: Moving ".$msg_uid." to ".$mailconf[0]['movetofolder']." for ".$mailconf[0]['user']); + imap_setflag_full($mbox, $msg_uid, "\\Seen", ST_UID); + if ($mailconf[0]['movetofolder'] != "") + imap_mail_move($mbox, $msg_uid, $mailconf[0]['movetofolder'], FT_UID); + break; + }*/ + continue; } - /*switch ($mailconf[0]['action']) { + + + // look for a 'references' or an 'in-reply-to' header and try to match with a parent item we have locally. + + // $raw_refs = ((x($headers,'references')) ? str_replace("\t",'',$headers['references']) : ''); + $raw_refs = ((property_exists($meta,'references')) ? str_replace("\t",'',$meta->references) : ''); + if(! trim($raw_refs)) + $raw_refs = ((property_exists($meta,'in_reply_to')) ? str_replace("\t",'',$meta->in_reply_to) : ''); + $raw_refs = trim($raw_refs); // Don't allow a blank reference in $refs_arr + + if($raw_refs) { + $refs_arr = explode(' ', $raw_refs); + if(count($refs_arr)) { + for($x = 0; $x < count($refs_arr); $x ++) + $refs_arr[$x] = "'" . msgid2iri(str_replace(array('<','>',' '),array('','',''),dbesc($refs_arr[$x]))) . "'"; + } + $qstr = implode(',',$refs_arr); + $r = q("SELECT `uri` , `parent-uri` FROM `item` WHERE `uri` IN ( $qstr ) AND `uid` = %d LIMIT 1", + intval($importer_uid) + ); + if(count($r)) + $datarray['parent-uri'] = $r[0]['parent-uri']; // Set the parent as the top-level item + // $datarray['parent-uri'] = $r[0]['uri']; + } + + + if(! x($datarray,'parent-uri')) + $datarray['parent-uri'] = $datarray['uri']; + + // Decoding the header + $subject = imap_mime_header_decode($meta->subject); + $datarray['title'] = ""; + foreach($subject as $subpart) + if ($subpart->charset != "default") + $datarray['title'] .= iconv($subpart->charset, 'UTF-8//IGNORE', $subpart->text); + else + $datarray['title'] .= $subpart->text; + + $datarray['title'] = notags(trim($datarray['title'])); + + //$datarray['title'] = notags(trim($meta->subject)); + $datarray['created'] = datetime_convert('UTC','UTC',$meta->date); + + // Is it reply? + $reply = ((substr(strtolower($datarray['title']), 0, 3) == "re:") or + (substr(strtolower($datarray['title']), 0, 3) == "re-") or + (raw_refs != "")); + + $r = email_get_msg($mbox,$msg_uid, $reply); + if(! $r) { + logger("Mail: can't fetch msg ".$msg_uid." for ".$mailconf[0]['user']); + continue; + } + $datarray['body'] = escape_tags($r['body']); + + logger("Mail: Importing ".$msg_uid." for ".$mailconf[0]['user']); + + // some mailing lists have the original author as 'from' - add this sender info to msg body. + // todo: adding a gravatar for the original author would be cool + + if(! stristr($meta->from,$contact['addr'])) { + $from = imap_mime_header_decode($meta->from); + $fromdecoded = ""; + foreach($from as $frompart) + if ($frompart->charset != "default") + $fromdecoded .= iconv($frompart->charset, 'UTF-8//IGNORE', $frompart->text); + else + $fromdecoded .= $frompart->text; + + $datarray['body'] = "[b]".t('From: ') . escape_tags($fromdecoded) . "[/b]\n\n" . $datarray['body']; + } + + $datarray['uid'] = $importer_uid; + $datarray['contact-id'] = $contact['id']; + if($datarray['parent-uri'] === $datarray['uri']) + $datarray['private'] = 1; + if(($contact['network'] === NETWORK_MAIL) && (! get_pconfig($importer_uid,'system','allow_public_email_replies'))) { + $datarray['private'] = 1; + $datarray['allow_cid'] = '<' . $contact['id'] . '>'; + } + $datarray['author-name'] = $contact['name']; + $datarray['author-link'] = 'mailbox'; + $datarray['author-avatar'] = $contact['photo']; + + $stored_item = item_store($datarray); + q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d", + dbesc($datarray['parent-uri']), + intval($importer_uid) + ); + q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d LIMIT 1", + intval($stored_item) + ); + switch ($mailconf[0]['action']) { case 0: logger("Mail: Seen before ".$msg_uid." for ".$mailconf[0]['user'].". Doing nothing.", LOGGER_DEBUG); break; @@ -339,119 +452,7 @@ function onepoll_run(&$argv, &$argc){ if ($mailconf[0]['movetofolder'] != "") imap_mail_move($mbox, $msg_uid, $mailconf[0]['movetofolder'], FT_UID); break; - }*/ - continue; - } - - - // look for a 'references' or an 'in-reply-to' header and try to match with a parent item we have locally. - -// $raw_refs = ((x($headers,'references')) ? str_replace("\t",'',$headers['references']) : ''); - $raw_refs = ((property_exists($meta,'references')) ? str_replace("\t",'',$meta->references) : ''); - if(! trim($raw_refs)) - $raw_refs = ((property_exists($meta,'in_reply_to')) ? str_replace("\t",'',$meta->in_reply_to) : ''); - $raw_refs = trim($raw_refs); // Don't allow a blank reference in $refs_arr - - if($raw_refs) { - $refs_arr = explode(' ', $raw_refs); - if(count($refs_arr)) { - for($x = 0; $x < count($refs_arr); $x ++) - $refs_arr[$x] = "'" . msgid2iri(str_replace(array('<','>',' '),array('','',''),dbesc($refs_arr[$x]))) . "'"; } - $qstr = implode(',',$refs_arr); - $r = q("SELECT `uri` , `parent-uri` FROM `item` WHERE `uri` IN ( $qstr ) AND `uid` = %d LIMIT 1", - intval($importer_uid) - ); - if(count($r)) - $datarray['parent-uri'] = $r[0]['parent-uri']; // Set the parent as the top-level item -// $datarray['parent-uri'] = $r[0]['uri']; - } - - - if(! x($datarray,'parent-uri')) - $datarray['parent-uri'] = $datarray['uri']; - - // Decoding the header - $subject = imap_mime_header_decode($meta->subject); - $datarray['title'] = ""; - foreach($subject as $subpart) - if ($subpart->charset != "default") - $datarray['title'] .= iconv($subpart->charset, 'UTF-8//IGNORE', $subpart->text); - else - $datarray['title'] .= $subpart->text; - - $datarray['title'] = notags(trim($datarray['title'])); - - //$datarray['title'] = notags(trim($meta->subject)); - $datarray['created'] = datetime_convert('UTC','UTC',$meta->date); - - // Is it reply? - $reply = ((substr(strtolower($datarray['title']), 0, 3) == "re:") or - (substr(strtolower($datarray['title']), 0, 3) == "re-") or - (raw_refs != "")); - - $r = email_get_msg($mbox,$msg_uid, $reply); - if(! $r) { - logger("Mail: can't fetch msg ".$msg_uid." for ".$mailconf[0]['user']); - continue; - } - $datarray['body'] = escape_tags($r['body']); - - logger("Mail: Importing ".$msg_uid." for ".$mailconf[0]['user']); - - // some mailing lists have the original author as 'from' - add this sender info to msg body. - // todo: adding a gravatar for the original author would be cool - - if(! stristr($meta->from,$contact['addr'])) { - $from = imap_mime_header_decode($meta->from); - $fromdecoded = ""; - foreach($from as $frompart) - if ($frompart->charset != "default") - $fromdecoded .= iconv($frompart->charset, 'UTF-8//IGNORE', $frompart->text); - else - $fromdecoded .= $frompart->text; - - $datarray['body'] = "[b]".t('From: ') . escape_tags($fromdecoded) . "[/b]\n\n" . $datarray['body']; - } - - $datarray['uid'] = $importer_uid; - $datarray['contact-id'] = $contact['id']; - if($datarray['parent-uri'] === $datarray['uri']) - $datarray['private'] = 1; - if(($contact['network'] === NETWORK_MAIL) && (! get_pconfig($importer_uid,'system','allow_public_email_replies'))) { - $datarray['private'] = 1; - $datarray['allow_cid'] = '<' . $contact['id'] . '>'; - } - $datarray['author-name'] = $contact['name']; - $datarray['author-link'] = 'mailbox'; - $datarray['author-avatar'] = $contact['photo']; - - $stored_item = item_store($datarray); - q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d", - dbesc($datarray['parent-uri']), - intval($importer_uid) - ); - q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d LIMIT 1", - intval($stored_item) - ); - switch ($mailconf[0]['action']) { - case 0: - logger("Mail: Seen before ".$msg_uid." for ".$mailconf[0]['user'].". Doing nothing.", LOGGER_DEBUG); - break; - case 1: - logger("Mail: Deleting ".$msg_uid." for ".$mailconf[0]['user']); - imap_delete($mbox, $msg_uid, FT_UID); - break; - case 2: - logger("Mail: Mark as seen ".$msg_uid." for ".$mailconf[0]['user']); - imap_setflag_full($mbox, $msg_uid, "\\Seen", ST_UID); - break; - case 3: - logger("Mail: Moving ".$msg_uid." to ".$mailconf[0]['movetofolder']." for ".$mailconf[0]['user']); - imap_setflag_full($mbox, $msg_uid, "\\Seen", ST_UID); - if ($mailconf[0]['movetofolder'] != "") - imap_mail_move($mbox, $msg_uid, $mailconf[0]['movetofolder'], FT_UID); - break; } } } diff --git a/include/security.php b/include/security.php index 0558df1a1a..56d4cad36f 100644 --- a/include/security.php +++ b/include/security.php @@ -1,6 +1,6 @@ identities,true), LOGGER_DEBUG); + if($login_refresh) + logger('auth_identities refresh: ' . print_r($a->identities,true), LOGGER_DEBUG); $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 1 LIMIT 1", intval($_SESSION['uid'])); @@ -76,7 +78,7 @@ function authenticate_success($user_record, $login_initial = false, $interactive header('X-Account-Management-Status: active; name="' . $a->user['username'] . '"; id="' . $a->user['nickname'] .'"'); - if($login_initial) { + if($login_initial || $login_refresh) { $l = get_browser_language(); q("UPDATE `user` SET `login_date` = '%s', `language` = '%s' WHERE `uid` = %d LIMIT 1", @@ -84,7 +86,8 @@ function authenticate_success($user_record, $login_initial = false, $interactive dbesc($l), intval($_SESSION['uid']) ); - + } + if($login_initial) { call_hooks('logged_in', $a->user); if(($a->module !== 'home') && isset($_SESSION['return_url'])) diff --git a/index.php b/index.php index 7d7674530a..bd5b3e396e 100644 --- a/index.php +++ b/index.php @@ -115,19 +115,9 @@ if(! x($_SESSION,'authenticated')) header('X-Account-Management-Status: none'); -/* - * Create the page head after setting the language - * and getting any auth credentials - */ - -$a->init_pagehead(); - -/** - * Build the page ending -- this is stuff that goes right before - * the closing