diff --git a/.gitignore b/.gitignore index 2531fe4cdd..5fe71a7a87 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ report/ .buildpath .externalToolBuilders .settings +#ignore OSX .DS_Store files +.DS_Store + diff --git a/.htaccess b/.htaccess index 6cb3a07494..b6b2538e71 100644 --- a/.htaccess +++ b/.htaccess @@ -15,6 +15,16 @@ Deny from all # Also place auth information into REMOTE_USER for sites running # in CGI mode. + # If you have troubles or use VirtualDocumentRoot + # uncomment this and set it to the path where your friendica installation is + # i.e.: + # Friendica url: http://some.example.com + # RewriteBase / + # Friendica url: http://some.example.com/friendica + # RewriteBase /friendica/ + # + #RewriteBase / + RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php?q=$1 [E=REMOTE_USER:%{HTTP:Authorization},L,QSA] diff --git a/INSTALL.txt b/INSTALL.txt index c36c846a18..294c6c9dd3 100644 --- a/INSTALL.txt +++ b/INSTALL.txt @@ -51,6 +51,10 @@ php.ini file [or see 'poormancron' in section 8] directory/path component in the URL) is preferred. This is REQUIRED if you wish to communicate with the Diaspora network. + + - For alternative server configurations (such as Nginx server and MariaDB + database engine), refer to the wiki at https://github.com/friendica/friendica/wiki + 2. Unpack the Friendica files into the root of your web server document area. - If you copy the directory tree to your webserver, make sure diff --git a/README b/README index d85d98aad1..ed57760512 100644 --- a/README +++ b/README @@ -8,4 +8,6 @@ Welcome to the free social web. Friendica is a communications platform for integrated social communications utilising decentralised communications and linkage to several indie social projects - as well as popular mainstream providers. -Our mission is to free our friends and families from the clutches of data-harvesting corporations, and pave the way to a future where social communications are free and open and flow between alternate providers as easily as email does today. \ No newline at end of file +Our mission is to free our friends and families from the clutches of data-harvesting corporations, and pave the way to a future where social communications are free and open and flow between alternate providers as easily as email does today. + +Report issues at http://bugs.friendica.com/ \ No newline at end of file diff --git a/boot.php b/boot.php index 5404faaa42..9e4c8f0552 100644 --- a/boot.php +++ b/boot.php @@ -8,11 +8,12 @@ require_once('include/datetime.php'); require_once('include/pgettext.php'); require_once('include/nav.php'); require_once('include/cache.php'); +require_once('library/Mobile_Detect/Mobile_Detect.php'); define ( 'FRIENDICA_PLATFORM', 'Friendica'); -define ( 'FRIENDICA_VERSION', '3.0.1398' ); +define ( 'FRIENDICA_VERSION', '3.0.1516' ); define ( 'DFRN_PROTOCOL_VERSION', '2.23' ); -define ( 'DB_UPDATE_VERSION', 1153 ); +define ( 'DB_UPDATE_VERSION', 1156 ); define ( 'EOL', "
\r\n" ); define ( 'ATOM_TIME', 'Y-m-d\TH:i:s\Z' ); @@ -191,6 +192,7 @@ define ( 'NOTIFY_SUGGEST', 0x0020 ); define ( 'NOTIFY_PROFILE', 0x0040 ); define ( 'NOTIFY_TAGSELF', 0x0080 ); define ( 'NOTIFY_TAGSHARE', 0x0100 ); +define ( 'NOTIFY_POKE', 0x0200 ); define ( 'NOTIFY_SYSTEM', 0x8000 ); @@ -215,7 +217,7 @@ define ( 'TERM_OBJ_PHOTO', 2 ); * various namespaces we may need to parse */ -define ( 'NAMESPACE_ZOT', 'http://purl.org/macgirvin/zot' ); +define ( 'NAMESPACE_ZOT', 'http://purl.org/zot' ); define ( 'NAMESPACE_DFRN' , 'http://purl.org/macgirvin/dfrn/1.0' ); define ( 'NAMESPACE_THREAD' , 'http://purl.org/syndication/thread/1.0' ); define ( 'NAMESPACE_TOMB' , 'http://purl.org/atompub/tombstones/1.0' ); @@ -250,6 +252,9 @@ define ( 'ACTIVITY_UPDATE', NAMESPACE_ACTIVITY_SCHEMA . 'update' ); define ( 'ACTIVITY_TAG', NAMESPACE_ACTIVITY_SCHEMA . 'tag' ); define ( 'ACTIVITY_FAVORITE', NAMESPACE_ACTIVITY_SCHEMA . 'favorite' ); +define ( 'ACTIVITY_POKE', NAMESPACE_ZOT . '/activity/poke' ); +define ( 'ACTIVITY_MOOD', NAMESPACE_ZOT . '/activity/mood' ); + define ( 'ACTIVITY_OBJ_COMMENT', NAMESPACE_ACTIVITY_SCHEMA . 'comment' ); define ( 'ACTIVITY_OBJ_NOTE', NAMESPACE_ACTIVITY_SCHEMA . 'note' ); define ( 'ACTIVITY_OBJ_PERSON', NAMESPACE_ACTIVITY_SCHEMA . 'person' ); @@ -278,7 +283,9 @@ define ( 'GRAVITY_COMMENT', 6); */ function startup() { + error_reporting(E_ERROR | E_WARNING | E_PARSE); + set_time_limit(0); // This has to be quite large to deal with embedded private photos @@ -345,11 +352,26 @@ if(! class_exists('App')) { public $plugins; public $apps = array(); public $identities; + public $is_mobile; + public $is_tablet; public $nav_sel; public $category; + // 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; + private $scheme; private $hostname; private $baseurl; @@ -383,7 +405,6 @@ if(! class_exists('App')) { elseif(x($_SERVER,'SERVER_PORT') && (intval($_SERVER['SERVER_PORT']) == 443)) $this->scheme = 'https'; - if(x($_SERVER,'SERVER_NAME')) { $this->hostname = $_SERVER['SERVER_NAME']; @@ -413,6 +434,7 @@ if(! class_exists('App')) { . 'include' . PATH_SEPARATOR . 'library' . PATH_SEPARATOR . 'library/phpsec' . PATH_SEPARATOR + . 'library/langdet' . PATH_SEPARATOR . '.' ); if((x($_SERVER,'QUERY_STRING')) && substr($_SERVER['QUERY_STRING'],0,2) === "q=") { @@ -453,6 +475,7 @@ if(! class_exists('App')) { $this->argc = count($this->argv); if((array_key_exists('0',$this->argv)) && strlen($this->argv[0])) { $this->module = str_replace(".", "_", $this->argv[0]); + $this->module = str_replace("-", "_", $this->module); } else { $this->argc = 1; @@ -460,16 +483,6 @@ if(! class_exists('App')) { $this->module = 'home'; } - /** - * Special handling for the webfinger/lrdd host XRD file - */ - - if($this->cmd === '.well-known/host-meta') { - $this->argc = 1; - $this->argv = array('hostxrd'); - $this->module = 'hostxrd'; - } - /** * See if there is any page number information, and initialise * pagination @@ -481,6 +494,14 @@ if(! class_exists('App')) { if($this->pager['start'] < 0) $this->pager['start'] = 0; $this->pager['total'] = 0; + + /** + * Detect mobile devices + */ + + $mobile_detect = new Mobile_Detect(); + $this->is_mobile = $mobile_detect->isMobile(); + $this->is_tablet = $mobile_detect->isTablet(); } function get_baseurl($ssl = false) { @@ -555,7 +576,7 @@ if(! class_exists('App')) { $interval = 40000; $this->page['title'] = $this->config['sitename']; - $tpl = file_get_contents('view/head.tpl'); + $tpl = get_markup_template('head.tpl'); $this->page['htmlhead'] = replace_macros($tpl,array( '$baseurl' => $this->get_baseurl(), // FIXME for z_path!!!! '$local_user' => local_user(), @@ -568,6 +589,13 @@ if(! class_exists('App')) { )); } + function init_page_end() { + $tpl = get_markup_template('end.tpl'); + $this->page['end'] = replace_macros($tpl,array( + '$baseurl' => $this->get_baseurl() // FIXME for z_path!!!! + )); + } + function set_curl_code($code) { $this->curl_code = $code; } @@ -710,9 +738,13 @@ if(! function_exists('check_config')) { // than the currently visited url, store the current value accordingly. // "Radically different" ignores common variations such as http vs https // and www.example.com vs example.com. + // We will only change the url to an ip address if there is no existing setting - if((! x($url)) || (! link_compare($url,$a->get_baseurl()))) + if(! x($url)) $url = set_config('system','url',$a->get_baseurl()); + if((! link_compare($url,$a->get_baseurl())) && (! preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/",$a->get_hostname))) + $url = set_config('system','url',$a->get_baseurl()); + if($build != DB_UPDATE_VERSION) { $stored = intval($build); @@ -743,9 +775,10 @@ if(! function_exists('check_config')) { // If the update fails or times-out completely you may need to // delete the config entry to try again. - if(get_config('database','update_' . $x)) + $t = get_config('database','update_' . $x); + if($t !== false) break; - set_config('database','update_' . $x, '1'); + set_config('database','update_' . $x, time()); // call the specific update @@ -768,13 +801,14 @@ if(! function_exists('check_config')) { . 'Content-transfer-encoding: 8bit' ); //try the logger logger('CRITICAL: Update Failed: '. $x); + break; } - else + else { set_config('database','update_' . $x, 'success'); - + set_config('system','build', $x + 1); + } } } - set_config('system','build', DB_UPDATE_VERSION); } } } @@ -873,6 +907,10 @@ if(! function_exists('login')) { $tpl = get_markup_template("logout.tpl"); } else { + $a->page['htmlhead'] .= replace_macros(get_markup_template("login_head.tpl"),array( + '$baseurl' => $a->get_baseurl(true) + )); + $tpl = get_markup_template("login.tpl"); $_SESSION['return_url'] = $a->query_string; } @@ -998,11 +1036,29 @@ if(! function_exists('get_max_import_size')) { if(! function_exists('profile_load')) { function profile_load(&$a, $nickname, $profile = 0) { - if(remote_user()) { - $r = q("SELECT `profile-id` FROM `contact` WHERE `id` = %d LIMIT 1", - intval($_SESSION['visitor_id'])); - if(count($r)) - $profile = $r[0]['profile-id']; + + $user = q("select uid from user where nickname = '%s' limit 1", + dbesc($nickname) + ); + + if(! ($user && count($user))) { + logger('profile error: ' . $a->query_string, LOGGER_DEBUG); + notice( t('Requested account is not available.') . EOL ); + $a->error = 404; + return; + } + + if(remote_user() && count($_SESSION['remote'])) { + foreach($_SESSION['remote'] as $visitor) { + if($visitor['uid'] == $user[0]['uid']) { + $r = q("SELECT `profile-id` FROM `contact` WHERE `id` = %d LIMIT 1", + intval($visitor['cid']) + ); + if(count($r)) + $profile = $r[0]['profile-id']; + break; + } + } } $r = null; @@ -1043,9 +1099,12 @@ if(! function_exists('profile_load')) { $a->profile = $r[0]; + $a->profile['mobile-theme'] = get_pconfig($profile_uid, 'system', 'mobile_theme'); + $a->page['title'] = $a->profile['name'] . " @ " . $a->config['sitename']; $_SESSION['theme'] = $a->profile['theme']; + $_SESSION['mobile-theme'] = $a->profile['mobile-theme']; /** * load/reload current theme info @@ -1117,8 +1176,14 @@ if(! function_exists('profile_sidebar')) { // don't show connect link to authenticated visitors either - if((remote_user()) && ($_SESSION['visitor_visiting'] == $profile['uid'])) - $connect = False; + if(remote_user() && count($_SESSION['remote'])) { + foreach($_SESSION['remote'] as $visitor) { + if($visitor['uid'] == $profile['uid']) { + $connect = false; + break; + } + } + } if(get_my_url() && $profile['unkmail']) $wallmessage = t('Message'); @@ -1234,9 +1299,15 @@ if(! function_exists('get_birthdays')) { $a = get_app(); $o = ''; - if(! local_user()) + if(! local_user() || $a->is_mobile || $a->is_tablet) return $o; +// $mobile_detect = new Mobile_Detect(); +// $is_mobile = $mobile_detect->isMobile() || $mobile_detect->isTablet(); + +// if($is_mobile) +// return $o; + $bd_format = t('g A l F d') ; // 8 AM Friday January 18 $bd_short = t('F d'); @@ -1313,9 +1384,16 @@ if(! function_exists('get_events')) { $a = get_app(); - if(! local_user()) + if(! local_user() || $a->is_mobile || $a->is_tablet) return $o; + +// $mobile_detect = new Mobile_Detect(); +// $is_mobile = $mobile_detect->isMobile() || $mobile_detect->isTablet(); + +// if($is_mobile) +// return $o; + $bd_format = t('g A l F d') ; // 8 AM Friday January 18 $bd_short = t('F d'); @@ -1427,7 +1505,10 @@ if(! function_exists('proc_run')) { $args[$x] = escapeshellarg($args[$x]); $cmdline = implode($args," "); - proc_close(proc_open($cmdline." &",array(),$foo)); + if(get_config('system','proc_windows')) + proc_close(proc_open('cmd /c start /b ' . $cmdline,array(),$foo,dirname(__FILE__))); + else + proc_close(proc_open($cmdline." &",array(),$foo,dirname(__FILE__))); } } @@ -1437,9 +1518,31 @@ if(! function_exists('current_theme')) { $a = get_app(); - $system_theme = ((isset($a->config['system']['theme'])) ? $a->config['system']['theme'] : ''); - $theme_name = ((isset($_SESSION) && x($_SESSION,'theme')) ? $_SESSION['theme'] : $system_theme); +// $mobile_detect = new Mobile_Detect(); +// $is_mobile = $mobile_detect->isMobile() || $mobile_detect->isTablet(); + $is_mobile = $a->is_mobile || $a->is_tablet; + if($is_mobile) { + if(isset($_SESSION['show-mobile']) && !$_SESSION['show-mobile']) { + $system_theme = ''; + $theme_name = ''; + } + else { + $system_theme = ((isset($a->config['system']['mobile-theme'])) ? $a->config['system']['mobile-theme'] : ''); + $theme_name = ((isset($_SESSION) && x($_SESSION,'mobile-theme')) ? $_SESSION['mobile-theme'] : $system_theme); + + if($theme_name === '---') { + // user has selected to have the mobile theme be the same as the normal one + $system_theme = ''; + $theme_name = ''; + } + } + } + if(!$is_mobile || ($system_theme === '' && $theme_name === '')) { + $system_theme = ((isset($a->config['system']['theme'])) ? $a->config['system']['theme'] : ''); + $theme_name = ((isset($_SESSION) && x($_SESSION,'theme')) ? $_SESSION['theme'] : $system_theme); + } + if($theme_name && (file_exists('view/theme/' . $theme_name . '/style.css') || file_exists('view/theme/' . $theme_name . '/style.php'))) @@ -1575,18 +1678,21 @@ if(! function_exists('profile_tabs')){ 'url' => $url, 'sel' => ((!isset($tab)&&$a->argv[0]=='profile')?'active':''), 'title' => t('Status Messages and Posts'), + 'id' => 'status-tab', ), array( 'label' => t('Profile'), 'url' => $url.'/?tab=profile', 'sel' => ((isset($tab) && $tab=='profile')?'active':''), 'title' => t('Profile Details'), + 'id' => 'profile-tab', ), array( 'label' => t('Photos'), 'url' => $a->get_baseurl() . '/photos/' . $nickname, 'sel' => ((!isset($tab)&&$a->argv[0]=='photos')?'active':''), 'title' => t('Photo Albums'), + 'id' => 'photo-tab', ), ); @@ -1596,12 +1702,14 @@ if(! function_exists('profile_tabs')){ 'url' => $a->get_baseurl() . '/events', 'sel' =>((!isset($tab)&&$a->argv[0]=='events')?'active':''), 'title' => t('Events and Calendar'), + 'id' => 'events-tab', ); $tabs[] = array( 'label' => t('Personal Notes'), 'url' => $a->get_baseurl() . '/notes', 'sel' =>((!isset($tab)&&$a->argv[0]=='notes')?'active':''), 'title' => t('Only You Can See This'), + 'id' => 'notes-tab', ); } @@ -1670,3 +1778,28 @@ function build_querystring($params, $name=null) { } return $ret; } + +/** +* Returns the complete URL of the current page, e.g.: http(s)://something.com/network +* +* Taken from http://webcheatsheet.com/php/get_current_page_url.php +*/ +function curPageURL() { + $pageURL = 'http'; + if ($_SERVER["HTTPS"] == "on") {$pageURL .= "s";} + $pageURL .= "://"; + if ($_SERVER["SERVER_PORT"] != "80" && $_SERVER["SERVER_PORT"] != "443") { + $pageURL .= $_SERVER["SERVER_NAME"].":".$_SERVER["SERVER_PORT"].$_SERVER["REQUEST_URI"]; + } else { + $pageURL .= $_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"]; + } + return $pageURL; +} + +function random_digits($digits) { + $rn = ''; + for($i = 0; $i < $digits; $i++) { + $rn .= rand(0,9); + } + return $rn; +} diff --git a/database.sql b/database.sql index 1d0a321760..0f82616f7b 100644 --- a/database.sql +++ b/database.sql @@ -260,6 +260,7 @@ CREATE TABLE IF NOT EXISTS `event` ( `type` char(255) NOT NULL, `nofinish` tinyint(1) NOT NULL DEFAULT '0', `adjust` tinyint(1) NOT NULL DEFAULT '1', + `ignore` tinyint(1) NOT NULL DEFAULT '0', `allow_cid` mediumtext NOT NULL, `allow_gid` mediumtext NOT NULL, `deny_cid` mediumtext NOT NULL, @@ -271,7 +272,8 @@ CREATE TABLE IF NOT EXISTS `event` ( KEY `type` ( `type` ), KEY `start` ( `start` ), KEY `finish` ( `finish` ), - KEY `adjust` ( `adjust` ) + KEY `adjust` ( `adjust` ), + KEY `ignore` ( `ignore` ) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; -- -------------------------------------------------------- @@ -456,6 +458,7 @@ CREATE TABLE IF NOT EXISTS `hook` ( `hook` char(255) NOT NULL, `file` char(255) NOT NULL, `function` char(255) NOT NULL, + `priority` int(11) UNSIGNED NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; @@ -569,6 +572,9 @@ CREATE TABLE IF NOT EXISTS `item` ( KEY `moderated` (`moderated`), KEY `spam` (`spam`), KEY `author-name` (`author-name`), + KEY `uid_commented` (`uid`, `commented`), + KEY `uid_created` (`uid`, `created`), + KEY `uid_unseen` (`uid`, `unseen`), FULLTEXT KEY `title` (`title`), FULLTEXT KEY `body` (`body`), FULLTEXT KEY `allow_cid` (`allow_cid`), @@ -586,11 +592,13 @@ CREATE TABLE IF NOT EXISTS `item` ( -- CREATE TABLE IF NOT EXISTS `item_id` ( + `id` int(11) NOT NULL AUTO_INCREMENT, `iid` int(11) NOT NULL, `uid` int(11) NOT NULL, `sid` char(255) NOT NULL, `service` char(255) NOT NULL, - PRIMARY KEY (`iid`), + PRIMARY KEY (`id`), + KEY `iid` (`iid`), KEY `uid` (`uid`), KEY `sid` (`sid`), KEY `service` (`service`) @@ -602,7 +610,7 @@ CREATE TABLE IF NOT EXISTS `item_id` ( -- Table structure for table `locks` -- -CREATE TABLE `locks` ( +CREATE TABLE IF NOT EXISTS `locks` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` char(128) NOT NULL, `locked` tinyint(1) NOT NULL DEFAULT '0', diff --git a/doc/Groups-and-Privacy.md b/doc/Groups-and-Privacy.md index 09c6a7349b..6e860b20d1 100644 --- a/doc/Groups-and-Privacy.md +++ b/doc/Groups-and-Privacy.md @@ -4,7 +4,9 @@ Groups and Privacy * [Home](help) -Groups are merely collections of friends. But Friendica uses these to unlock some very powerful features. +Groups are merely collections of friends. But Friendica uses these to unlock some very powerful features. + +**Setting Up Groups** To create a group, visit your Friendica "Contacts" page and select "Create a new group". Give the group a name. @@ -14,6 +16,8 @@ You will have two boxes on this page. The top box is the roster of current group If you click on a photo of a person who isn't in the group, they will be put into the group. If you click on a photo of a person who is in the group, they will be removed from it. +**Access Control** + Once you have created a group, you may use it in any access control list. This is the little lock icon beneath the status update box on your home page. If you click this you can select who can see and who can *not* see the post you are about to make. These can be individual people or groups. On your "Network" page you will find posts and conversation from everybody in your network. You may select an individual group on this page to show conversations pertaining only to members of that group. @@ -22,32 +26,44 @@ But wait, there's more... If you look carefully when visiting a group from your Network page, the lock icon under the status update box has an exclamation mark next to it. This is meant to draw attention to that lock. Click the lock. You will see that since you are only viewing a certain group of people, your status updates while on that screen default to only being seen by that same group of people. This is how you keep your future employers from seeing what you write to your drinking buddies. You can over-ride this setting, but this makes it easy to separate your conversations into different friend circles. +**Default Post Privacy** + +By default, Friendica assumes that you want all of your posts to be private. Therefore, when you sign up, Friendica creates a group for you that it will automatically add all of your contacts to. All of your posts are restricted to that group by default. + +Note that this behavior can be overridden by your site admin, in which case your posts will be "public" (i.e. visible to the entire Internet) by default. + +If you want your posts to be "public" by default, you can change your default post permissions on your Settings page. You also have the option there to change which groups you post to by default, or to change which group your new contacts get placed into by default. + +**Privacy Concerns To Be Aware Of** + These private conversations work best when your friends are Friendica members. We know who else can see the conversations - nobody, *unless* your friends cut and paste the messages and send them to others. This is a trust issue you need to be aware of. No software in the world can prevent your friends from leaking your confidential and trusted communications. Only a wise choice of friends. But it isn't as clear cut when dealing with status.net, identi.ca and other network providers. You are encouraged to be **very** cautious when other network members are in a group because it's entirely possible for your private messages to end up in a public newsfeed. If you look at the Contact Edit page for any person, we will tell you whether or not they are members of an insecure network where you should exercise caution. -On your "Settings" page, you may create a set of default permissions which apply to every post that you create. - Once you have created a post, you can not change the permissions assigned. Within seconds it has been delivered to lots of people - and perhaps everybody it was addressed to. If you mistakenly created a message and wish you could take it back, the best you can do is to delete it. We will send out a delete notification to everybody who received the message - and this should wipe out the message with the same speed it was initially propagated. In most cases it will be completely wiped from the Internet - in under a minute. Again, this applies to Friendica networks. Once a message spreads to other networks, it may not be removed quickly and in some cases it may not be removed at all. -In case you haven't yet figured this out, we are encouraging you to encourage your friends to use Friendica - because all these privacy features work much better within a privacy-aware network. Many of the other social networks Friendica can connect to have no privacy controls. +In case you haven't yet figured this out, we are encouraging you to encourage your friends to use Friendica - because all these privacy features work much better within a privacy-aware network. Many of the other social networks Friendica can connect to have no privacy controls. -Profiles, Privacy, and Photos +Profiles, Photos, and Privacy ============================= The decentralised nature of Friendica (many websites exchanging information rather than one website which controls everything) has some implications with privacy as it relates to people on other sites. There are things you should be aware of, so you can decide best how to interact privately. +**Photos** + Sharing photos privately is a problem. We can only share them __privately__ with Friendica members. In order to share with other people, we need to prove who they are. We can prove the identity of Friendica members, as we have a mechanism to do so. Your friends on other networks will be blocked from viewing these private photos because we cannot prove that they should be allowed to see them. Our developers are working on solutions to allow access to your friends - no matter what network they are on. However we take privacy seriously and don't behave like some networks that __pretend__ your photos are private, but make them available to others without proof of identity. +**Profiles** + Your profile and "wall" may also be visited by your friends from other networks, and you can block access to these by web visitors that Friendica doesn't know. Be aware that this could include some of your friends on other networks. This may produce undesired results when posting a long status message to (for instance) Twitter and even Facebook. When Friendica sends a post to these networks which exceeds the service length limit, we truncate it and provide a link to the original. The original is a link back to your Friendica profile. As Friendica cannot prove who they are, it may not be possible for these people to view your post in full. For people in this situation we would recommend providing a "Twitter-length" summary, with more detail for friends that can see the post in full. -Blocking your profile or entire Friendica site from unknown web visitors also has serious implications for communicating with StatusNet/identi.ca members. These networks communicate with others via public protocols that are not authenticated. In order to view your posts, these networks have to access them as an "unknown web visitor". If we allowed this, it would mean anybody could in fact see your posts, and you've instructed Friendica not to allow this. So be aware that the act of blocking your profile to unknown visitors also has the effect of blocking outbound communication with public networks (such as identi.ca) and feed readers such as Google Reader. \ No newline at end of file +Blocking your profile or entire Friendica site from unknown web visitors also has serious implications for communicating with StatusNet/identi.ca members. These networks communicate with others via public protocols that are not authenticated. In order to view your posts, these networks have to access them as an "unknown web visitor". If we allowed this, it would mean anybody could in fact see your posts, and you've instructed Friendica not to allow this. So be aware that the act of blocking your profile to unknown visitors also has the effect of blocking outbound communication with public networks (such as identi.ca) and feed readers such as Google Reader. diff --git a/doc/Home.md b/doc/Home.md index 30efc93f7d..1df74b5f26 100644 --- a/doc/Home.md +++ b/doc/Home.md @@ -6,6 +6,8 @@ Friendica Documentation and Resources * [Account Basics](help/Account-Basics) * [New User Quick Start](help/guide) +* [Creating posts](help/Text_editor) +* [Comment, sort and delete posts](help/Text_comment) * [Profiles](help/Profiles) * [Connectors](help/Connectors) * [Making Friends](help/Making-Friends) diff --git a/doc/Installing-Connectors.md b/doc/Installing-Connectors.md index 328e3b6c48..83d6954e76 100644 --- a/doc/Installing-Connectors.md +++ b/doc/Installing-Connectors.md @@ -100,20 +100,17 @@ b. The url should be your site URL with a trailing slash. You **may** be required to provide a privacy and/or terms of service URL. -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 +c. Navigate to Set Web->Site URL & Domain -> Website Settings. Set Site URL to yoursubdomain.yourdomain.com. Set Site Domain to your yourdomain.com. +d. Install the Facebook plugin on your Friendica site at 'admin/plugins'. You should then see a link for Facebook under 'Plugin Features' on the sidebar of the admin panel. Select that. -On Friendica, visit the Facebook Settings section of the "Settings->Connector Settings" page. And click 'Install Facebook Connector'. +e. Enter the App-ID and App Secret that Facebook gave you. Change any other settings as desired. + + +On Friendica, each member who wishes to use the Facebook connector should visit the Facebook Settings section of their "Settings->Connector Settings" page, and click 'Install Facebook Connector'. + +Choose the appropriate settings for your usage and privacy requirements. This will ask you to login to Facebook and grant permission to the plugin to do its stuff. Allow it to do so. diff --git a/doc/Text_comment.md b/doc/Text_comment.md new file mode 100644 index 0000000000..2aac568378 --- /dev/null +++ b/doc/Text_comment.md @@ -0,0 +1,44 @@ +Comment, sort and delete posts +============== + +* [Home](help) + +Here you can find an overview of the different ways to comment and sort existing posts. Attention: we've used the "diabook" theme. If you're using another theme, some of the icons may be different. + +diabook + +The different icons + +post_thumbs_up.png This symbol is used to indicate that you like the post. Click it twice to undo your choice.

+ +post_thumbs_down.png This symbol is used to indicate that you dislike the post. Click it twice to undo your choice. +

+ +post_share.png This symbol is used to share a post. A copy of this post will automatically appear in your status editor and add a link to the original post. +

+ +post_mark.png This symbol is used to mark a post. Marked posts will appear on your network page at the "starred" tab (from "star"). Click it twice to undo your choice. +

+ +post_tag.png This symbol is used to tag a post with a self-chosen keyword. When you click at the word, you'll get a list of all posts with this tag. Attention: you can't delete the tag once you've set one. +

+ +post_categorize.png This symbol is used to categorize posts. Choose an existing folder or create a new one. You'll find the created folder on your network page under the "saved folders" tab. +

+ +post_delete.png This symbol is used to delete your own post or to remove a post of another person from your stream. +

+ +post_choose.png This symbol is used to choose more than one post to delete in a single step. After selecting all posts, go to the end of the page and click "Delete Selected Items".

+ +**Symbols of other themes** + +Darkbubble darkbubble.png + +Darkzero darkzero.png + +(incl. more "zero"-themes, slackr, comix, easterbunny, facepark) + +Dispy dispy.png (incl. smoothly, testbubble) + +Frost Mobile frost.png diff --git a/doc/Text_editor.md b/doc/Text_editor.md new file mode 100644 index 0000000000..fa4393f009 --- /dev/null +++ b/doc/Text_editor.md @@ -0,0 +1,40 @@ +Creating posts +================= + +* [Home](help) + +Here you can find an overview of the different ways to create and edit your post. Attention: we've used the "diabook" theme. If you're using another theme, some of the icons may be different. + +editor + +The different iconss + +editor This symbol is used to upload a picture from your computer. If you only want to add an adress (url), you can also use the "tree" icon at the upper part of the editor. After selecting an image, you'll see a thumbnail in the editor. +

+ +paper_clip This symbol is used to add files from your computer. There'll be no preview of the content. +

+ +chain This symbol is used to add a web address (url). You'll see a short preview of the website. +

+ +video This symbol is used to add a web address (url) of a video file. You'll see a small preview of the video. +

+ +mic This symbol is used to add a web address (url) of an audio file. You'll see a player in your completed post. +

+ +globe This symbol is used to add your geographic location. This location will be added into a Google Maps search. That's why a note like "New York" or "10004" is already enough. +

+ +**Symbols of other themes** + +Cleanzero cleanzero.png + +(incl. more "zero"-themes, comix, easterbunny, facepark, slackr + +Darkbubble darkbubble.png (inkl. smoothly, testbubble) + +Frost frost.png + +Vier vier.png (inkl. dispy) diff --git a/doc/de/Account-Basics.md b/doc/de/Account-Basics.md new file mode 100644 index 0000000000..3319560e7f --- /dev/null +++ b/doc/de/Account-Basics.md @@ -0,0 +1,77 @@ +Account - Basics +============== + +* [Zur Startseite der Hilfe](help) + + +**Registrierung** + +Nicht alle Friendica-Seiten bieten eine freie Registrierung. Wenn die Registrierung erlaubt ist, zeigt sich sofort ein "Registrieren"-Link unter dem Login-Feld auf der Startseite. Ein Klick auf diesen Link führt zur Registrierungsseite. Die Stärke unseres Netzwerks ist, dass viele verschiedene Seiten komplett kompatible zueinander sind. Wenn die Seite, die du besuchst, eine Registrierung nicht erlaubt oder wenn du glaubst, dass dir eine andere Seite möglicherweise besser gefällt, dann kannst du hier eine Liste von öffentlichen Servern finden und die Seite suchen, die zu deinen Anforderungen passt. + +Wenn du deinen eigenen Server aufsetzen willst, kannst du das ebenfalls machen. Besuche die Friendica-Webseite, um den Code mit den Installationsanleitungen herunterzuladen. Es ist ein einfacher Installationsprozess, den jeder mit ein wenig Erfahrungen im Webseiten-Hosting oder mit grundlegenden Linux-Erfahrungen einfach handhaben kann. + + +*OpenID* + +Das erste Feld auf der Registrierungsseite ist für eine OpenID-Adresse. Wenn du keine OpenID-Adresse hast oder nicht wünschst, diese zu nutzen, dann lasse das Feld frei. Wenn du einen OpenID-Account hast und diesen nutzen willst, gib die Adresse in das Feld ein und klicke auf "Registrieren". Friendica wird versuchen, so viele Informationen wie möglich von deinem OpenID-Provider zu übernehmen, um diese in dein Profil auf dieser Seite einzutragen. + + +*Dein vollständiger Name* + +Bitte trage deinen vollständigen Namen **so ein, wie du ihn im System anzeigen lassen willst**. Viele Leute nutzen ihren richtigen Namen hierfür, allerdings besteht für dich keine Pflicht, das auch so zu machen. + + +*Email-Adresse* + +Bitte trage eine richtige Email-Adresse ein. Deine Email-Adresse wird **niemals** veröffentlicht. Wir benötigen diese, um dir Account-Informationen und die Login-Daten zu schicken. Du erhältst zudem von Zeit zu Zeit Benachrichtigungen über eingegangene Nachrichten oder Punkte, die deine Aufmerksamkeit benötigen. Du hast aber auch die Möglichkeit, diese Nachrichten in deinen Account-Einstellungen komplett abzuschalten. Du musst nicht deine Haupt-Email-Adresse sein, jedoch wird eine funktionierende Adresse benötigt. Ohne dieses kannst du weder dein Initialpasswort erhalten, noch dein Passwort zurücksetzen. Dies ist die einzige persönliche Information, die korrekt sein muss. + + +*Spitzname/Nickname* + +Der Spitzname wird benötigt, um eine Webadresse für viele deiner persönlichen Seiten zu erstellen. Auch wird dieser wie eine Email-Adresse genutzt, wenn eine Verbindung zu anderen Personen hergestellt werden soll. Durch die Art, wie der Spitzname genutzt wird, gibt es bestimmte Einschränkungen. Er darf nur US-ASCII-Textzeichen und Nummern enthalten und er muss zudem mit einem Buchstaben beginnen. Er muss außerdem einzigartig im System sein. Dieser Spitzname wird an vielen Stellen genutzt, um deinen Account zu identifizieren, und kann daher später nicht mehr geändert werden. + + +*Verzeichnis-Eintrag* + +Das Registrierungsformular erlaubt es dir, direkt auszuwählen, ob du im Onlineverzeichnis aufgelistet wirst oder nicht. Das ist wie ein Telefonbuch und du kannst entscheiden, nicht aufgeführt zu werden. Wir bitten dich, "Ja" zu wählen, so dass dich andere Leute (Freunde, Familie etc.) finden können. Wenn du "Nein" wählst, wirst du hauptsächlich unsichtbar sein und nur wenige Möglichkeiten zur Interaktion haben. Was auch immer du wählst, kann jederzeit nach dem Login in deinen Account-Einstellungen geändert werden. + + +*Registrierung* + +Sobald du die nötigen Informationen eingegeben hast, klicke auf "Registrieren". Eine Email wird an die hinterlegte Email-Adresse geschickt. Bitte prüfe den Posteingang (inkl. dem Spam-Ordner) für die Registrierungsdetails und dein Initialpasswort. + + +**Login-Seite** + +Gib auf der "Login"-Seite die Informationen ein, die du während der Registrierung erhalten hast. Du kannst entweder deinen Spitznamen oder die Email-Adresse als Login-Namen nutzen. + +Wenn du deinen Account nutzt, um mehrfache '[Seiten](help/Pages)' zu verwalten, die die gleiche Email-Adresse benutzen, dann nutze bitte den Spitznamen des Accounts, der verwaltet werden soll. + +*Wenn* dein Account OpenID nutzt, dann kannst du deine OpenID-Adresse als Login-Name nutzen und das Passwort-Feld frei lassen. Du wirst zu deinem OpenID-Anbieter weitergeleitet, wo du deine Anmeldung abschließt. + +Wenn OpenID nicht genutzt wird, gib dein Passwort ein. Dieses hast du zu Beginn in der Registrierungsmail erhalten. Dein Passwort ist zeichengenau; Groß- und Kleinschreibung wird beachtet. Prüfe bitte, ob deine Feststelltaste aktiv ist, falls du Schwierigkeiten beim Login hast. + + +**Passwort ändern** + +Besuche nach deinem ersten Login bitte die Einstellungsseite und wechsle das Passwort in eines, dass du dir merken kannst. + + +**Der Anfang** + +Ein ['Tipp für neue Mitglieder'](newmember)-Link zeigt sich in den ersten beiden Wochen auf deiner Startseite, um dir erste Informationen zum Start zu bieten. + + +**Persönliche Daten exportieren** + +Du kannst eine Kopie deiner persönlichen Daten in einer XML-Datei exportieren. Gehe hierzu in deinen Einstellungen auf "Persönliche Daten exportieren". + + +**Schau dir ebenfalls folgende Seiten an** + +* [Profile](help/Profiles) + +* [Gruppen und Privatssphäre](help/Groups-and-Privacy) + +* [Account löschen](help/Remove-Account) + diff --git a/doc/de/Bugs-and-Issues.md b/doc/de/Bugs-and-Issues.md new file mode 100644 index 0000000000..7d8ea9348a --- /dev/null +++ b/doc/de/Bugs-and-Issues.md @@ -0,0 +1,28 @@ +Bugs und Probleme +=============== + +* [Zur Startseite der Hilfe](help) + +Wenn dein Server eine Supportseite hat, solltest du jeden Bug und jedes Problem, den/das du findest, zunächst dort melden. Die Fehler zunächst dort zu melden, statt auf der allgemeinen Bug-Seite, erleichtert es den Entwicklern, neue Features zu entwickeln, wenn sie sich nicht mit Fehlern beschäftigen müssen, mit denen sie nichts zu tun haben. + +Wenn du ein technisch verantwortlicher Nutzer bist oder wenn deine Seite keine Support-Seite hat, dann kannst du den Bug Tracker nutzen. Bitte nutze zunächst die Suche, ob es bereits einen offenen Bug gibt, der deiner Anfrage entspricht. + +Versuche, so viele Informationen wie möglich zum Bug zu bieten. Hierzu gehört auch die **komplette** Fehlermeldung oder Notiz und alle Schritte, die zu dem Fehler geführt haben. Es ist generell besser, zu viele Informationen zu liefern, als zu wenige. + +Lies dir diesen Artikel (mehrsprachig) durch, um mehr über **gute** Bug-Reports zu erfahren. + +**Bug-Bearbeitung sponsern** + +Wenn du einen Bug findest, der seine Ursache im Hauptsystem hat (also wenn es sich nicht nur um deine Seite handelt), dann kannst du diesen sponsern. + +Die Bug/Fehler-Datenbank erlaubt es dir, Fehler zu sponsern. Das schafft einen Anreiz für die Entwickler, deinen Fehler zu bearbeiten. Das ist nicht zwingend notwendig, da wir keine Bugs mögen und versuchen, diese zu beheben. Wichtiger ist dieses für die zukünftige Projektentwicklung und für Feature-Anfragen. + +Bug-Sponsoring arbeitet nach dem System der Anerkennung. Wenn du 10€ spendest, um einen Bug zu beheben, dann sende die Zahlung per PayPal an den Entwickler, der den Bug behoben hat. Und denke nie daran, für geleistete Arbeit nicht zu bezahlen. Einige dieser Leute könnten deine Kreditkarte hacken, falls du sie verärgern solltest. + +Zur Zeit können nur Personen gesponserte Bugs bearbeiten, die als Entwickler bestätigt wurden. Hierfür muss der Entwickler bereits einige Friendica-Bugs bearbeitet haben. Das dient zur Absicherung, damit der behobene Bug auch gut mit Friendica läuft. Wenn du wünschst, als Entwickler bestätigt zu werden, dann arbeite dich ein und übernimm einige nicht-gesponserte Probleme oder dein eigenes Projekt und du wirst auf der Leiter nach oben klettern. + +Wenn du sicher glaubst, dass du einen gesponserten Bug beheben kannst, aber nicht als Entwickler bestätigt bist, kann es passieren, dass ein gesponserter Entwickler den Bug bearbeiten, bevor du ihn dir sichern kannst. Wenn das nicht der Fall ist, dann trage einen kleinen Vermerk in die Anfrage ein. Wenn du unsere Code-Standards erfüllst, versuchen wir, dir einen Bonus zukommen zu lassen. + +Wenn du ein Projekt mit mehr als 50€ sponserst, dann fragen dich die Entwickler gegebenenfalls, ob sie einen Teil der Zahlung vorab erhalten (normalerweise die Hälfte). Nochmals: es handelt sich um ein Anerkennungssystem - und hauptsächlich dient es dazu, Zahlungsprobleme und Streitigkeiten zu verhindern. Du solltest nach 1-2 Wochen einen gewissen Fortschritt oder Demonstrationen erwarten können. Wenn die Arbeit nicht in einer für dich annehmbaren Zeit gelöst ist, hast du das Recht, das Geld zurückzufordern. + +Friendica ist nicht in diese Transaktionen involviert. Es handelt sich ausschließlich um ein persönliches Abkommen zwischen dem Sponsor und dem Entwickler. Wenn es irgendwelche Probleme gibt, müssen die Parteien es untereinander klären. Wir erstellen gerade einige Richtlinien, um potentielle Probleme zu vermeiden. diff --git a/doc/de/Connectors.md b/doc/de/Connectors.md new file mode 100644 index 0000000000..52f93c8830 --- /dev/null +++ b/doc/de/Connectors.md @@ -0,0 +1,67 @@ +Konnektoren (Connectors) +========== + +* [Zur Startseite der Hilfe](help) + +Konnektoren erlauben es dir, dich mit anderen sozialen Netzwerken zu verbinden. Konnektoren werden nur bei bestehenden Facebook-, Twitter und StatusNet-Accounts benötigt. Außerdem gibt es einen Konnektor, um deinen Email-Posteingang zu nutzen. + +Wenn die folgenden Netzwerk-Konnektoren auf deinem System installiert sind, kannst du mit den folgenden Links die Einstellungsseiten besuchen und für deinen Account konfigurieren: + +* [Facebook](/settings/addon) +* [Twitter](/settings/addon) +* [StatusNet](/settings/addon) +* [Email](/settings) + +Anleitung, um sich mit Personen in bestimmten Netzwerken zu verbinden +========================================================== + +**Friendica** + +Du kannst dich verbinden, indem du deine Identitäts-Adresse auf der "Verbinden"-Seite des Friendica-Nutzers eingibst. Ebenso kannst du deren Identitäts-Adresse in der "Verbinden"-Box auf deiner ["Kontakt"-Seite](contacts) eingeben. + + +**Diaspora** + +Füge die Diaspore-Identitäts-Adresse (z.B. name@diasporapod.com)auf deiner ["Kontakte"-Seite](contacts) in das Feld "Neuen Kontakt hinzufügen" ein. + + +**Identi.ca/StatusNet/GNU-Social** + +Diese Netzwerke werden als "federated social web" bzw. "OStatus"-Kontakte bezeichnet. + +Bitte beachte, dass es **keine** Einstellungen zur Privatssphäre im OStatus-Netzwerk gibt. **Jede** Nachricht, die an eines dieser OStatus-Mitglieder verschickt wird, ist für jeden auf der Welt sichtbar; alle Privatssphäreneinstellungen verlieren ihre Wirkung. Diese Nachrichten erscheinen ebenfalls in öffentlichen Suchergebnissen. + +Da die OStatus-Kommunikation keine Authentifizierung benutzt, können OStatus-Nutzer *keine* Nachrichten empfangen, wenn du in deinen Privatssphäreneinstellungen "Profil und Nachrichten vor Unbekannten verbergen" wählst. + +Um dich mit einem OStatus-Mitglied zu verbinden, trage deren Profil-URL oder Identitäts-Adresse auf deiner ["Kontakte"-Seite](contacts) in das Feld "Neuen Kontakt hinzufügen" ein. + +Der StatusNet-Konnektor kann genutzt werden, wenn du Beiträge schreiben willst, die auf einer OStatus-Seite über einen existierenden OStatus-Account erscheinen sollen. + +Das ist nicht notwendig, wenn du OStatus-Mitgliedern von Friendica aus folgst und diese dir auch folgen, indem sie auf deiner Kontaktseite ihre eigene Identitäts-Adresse eingeben. + + +**Blogger, Wordpress, RSS feeds, andere Webseiten** + +Trage die URL auf deiner ["Kontakte"-Seite](contacts) in das Feld "Neuen Kontakt hinzufügen" ein. Du hast keine Möglichkeit, diesen Kontakten zu antworten. + +Das erlaubt dir, dich mit Millionen von Seiten im Internet zu _verbinden_. Alles, was dafür nötig ist, ist dass die Seite einen Feed im RSS- oder Atom Syndication-Format nutzt und welches einen Autoren und ein Bild zur Seite liefert. + + +**Twitter** + +Um einem Twitter-Nutzer zu folgen, trage die URL der Hauptseite des Twitter-Accounts auf deiner ["Kontakte"-Seite](contacts) in das Feld "Neuen Kontakt hinzufügen" ein. Um zu antworten, musst du den Twitter-Konnektor installieren und über deinen eigenen Status-Editor antworten. Beginne deine Nachricht mit @twitternutzer, ersetze das aber durch den richtigen Twitter-Namen. + + +**Email** + +Konfiguriere den Email-Konnektor auf deiner [Einstellungsseite](settings). Wenn du das gemacht hast, kannst du auf deiner ["Kontakte"-Seite](contacts) die Email-Adresse in das Feld "Neuen Kontakt hinzufügen" eintragen. Diese Email-Adresse muss jedoch bereits mit einer Nachricht in deinem Email-Posteingang auf dem Server liegen. Du hast die Möglichkeit, Email-Kontakte in deine privaten Unterhaltungen einzubeziehen. + +**Facebook** + +Der Facebook-Konnektor ist ein Plugin/Addon, dass es dir erlaubt, von Friendica aus mit Freunden auf Facebook zu interagieren. Wenn er aktiviert ist, wird deine Facebook-Freundesliste importiert und du wirst Facebook-Beiträge sehen und kommentieren können. Facebook-Freunde können außerdem zu privaten Gesprächen hinzugefügt werden. Du hast nicht die Möglichkeit, einzelne Facebook-Accounts hinzuzufügen, sondern nur deine gesamte Freundesliste, die aktualisiert wird, wenn neue Freunde hinzugefügt werden. + +Wenn das Facebook-Plugin/Addon installiert ist, kannst du diesen auf deiner Einstellungsseite unter ["Facebook Connector Settings"](settings/addon) einstellen. Dieser Eintrag erscheint nur, wenn das Plugin/Addon installiert ist. Folge den Vorgaben, um den Facebook-Konnektor zu installieren oder löschen. + +Du kannst ebenfalls auswählen, ob deine öffentlichen Posts auch standardmäßig bei Facebook veröffentlicht werden sollen. Du kannst diese Einstellung jederzeit im aktuellen Beitrag beeinflussen, indem du auf das "Schloss"-Icon unter dem Beitragseditor gehst. Diese Einstellung hat keine Auswirkung auf private Unterhaltungen. Diese werden immer an Facebook-Freunde mit den entsprechenden Genehmigungen geschickt. + +(Beachte: Aktuell können Facebook-Kontakte keine privaten Fotos sehen. Das wird zukünftig gelöst. Facebook-Kontakte können aber trotzdem öffentliche Fotos sehen, die du hochgeladen hast.) diff --git a/doc/de/Developers.md b/doc/de/Developers.md new file mode 100644 index 0000000000..f5c90c489a --- /dev/null +++ b/doc/de/Developers.md @@ -0,0 +1,22 @@ +Friendica - Entwickler-Guide +========== + +* [Zur Startseite der Hilfe](help) + +Hier erfährst du, wie du bei uns mitmachen kannst + +Zunächst erstelle dir ein funktionierendes Git-Paket auf deinem System, auf dem du die Entwicklung durchführst. + +Erstelle deinen eigenen Github-Account. + +Du hast die Möglichkeit, die Friendica-Daten direkt über Github von der folgenden Seite laden: [https://github.com/friendica/friendica.git](https://github.com/friendica/friendica.git) + +Befolge die Anleitung unter diesem Link [http://help.github.com/fork-a-repo/](http://help.github.com/fork-a-repo/), um deine eigene Kopie (fork) der Ursprungsdaten auf Github zu erstellen und bearbeiten. + +Gehe nun zu deiner Github-Seite und erstelle eine "Pull request", wenn du soweit bist, dein Projekt wieder in das Hauptprojekt einzugliedern. + +**Wichtig** + +Bitte hole dir alle Änderungen aus dem Projektverzeichnis und führe sie mit deiner Arbeit zusammen, **bevor** du deine "pull request" stellt. Wir behalten es uns vor, Patches abzulehnen, die eine große Anzahl an Fehlern hervorrufen. Dies gilt vor allem für Übersetzungen, da wir hier möglicherweise nicht alle feinen Unterschiede in konfliktären Versionen erkennen können. + +Außerdem: **teste deine Änderungen!** Vergiss nicht, dass eine simple Fehlerlösung einen anderen Fehler auslösen kann. Lass deine Änderungen von einem erfahrenen Friendica-Entwickler gegenprüfen. diff --git a/doc/de/Groups-and-Privacy.md b/doc/de/Groups-and-Privacy.md new file mode 100644 index 0000000000..90487e8983 --- /dev/null +++ b/doc/de/Groups-and-Privacy.md @@ -0,0 +1,68 @@ +Gruppen und Privatsphäre +================== + +* [Zur Startseite der Hilfe](help) + +Gruppen sind nur eine Ansammlung von Freunden. Aber Friendica nutzt diese, um sehr mächtige Features zur Verfügung zu stellen. + +**Gruppen erstellen** + +Um eine Gruppe zu erstellen, besuche deine "Kontakte"-Seite und wähle "Neue Gruppe erstellen" (je nach Design nur als Pluszeichen angezeigt). Gib deiner Gruppe einen Namen. + +Das führt dich zu einer Seite, auf der du die Gruppenmitglieder auswählen kannst. + +Du hast zwei Boxen auf der Seite. Die obere Box ist die Übersicht der aktuellen Mitglieder. Die untere beinhaltet alle Freunde, die *nicht* Mitglied dieser Gruppe sind. + +Wenn du auf das Foto einer Person klickst, die nicht in der Gruppe ist, wird diese in die Gruppe verschoben. Wenn du auf das Foto einer Person klickst, die bereits in der Gruppe ist, dann wird diese Person daraus entfernt. + +**Zugriffskontrolle** + +Sobald du eine Gruppe erstellt hast, kannst du diese auf jeder Zugriffsrechteliste nutzen. Damit ist das kleine Schloss neben deinem Statuseditor auf deiner Startseite gemeint. Wenn du darauf klickst, kannst du auswählen, wer deinen Beitrag sehen kann und wer *nicht*. Dabei kann es sich um eine einzelne Person oder eine ganze Gruppe handeln. + +Auf deiner "Netzwerk"-Seite ("Unterhaltungen deiner Kontakte") findest du Beiträge und Gespräche aller deiner Kontakte in deinem Netzwerk. Du kannst aber auch eine einzelne Gruppe auswählen und nur Beiträge dieser Gruppenmitglieder anzeigen lassen. + +Aber stopp, es gibt noch mehr... + +Wenn du auf deiner "Netzwerk"-Seite eine bestimmte Gruppe ausgewählt hast, dann findest du im Statuseditor neben dem Schloss ein Ausrufezeichen. Dies dient dazu, deine Aufmerksamkeit auf das Schloss zu richten. Klicke auf das Schloss. Dort siehst du, dass dein Status-Update in dieser Ansicht standardmäßig nur für diese Gruppe freigegeben ist. Das hilft dir, deinen zukünftigen Mitarbeitern nicht das Gleiche zu schreiben wie deinen Trinkfreunden. Du kannst diese Einstellung natürlich auch überschreiben. + +**Standardmäßige Zugriffsrechte von Beiträgen** + +Standardmäßig geht Friendica davon aus, dass alle deine Beiträge privat sein sollen. Aus diesem Grund erstellt Friendica nach der Anmeldung eine Gruppe, in die automatisch alle deine Kontakte hinzugefügt werden. Alle deine Beiträge sind nur auf diese Gruppe beschränkt. + +Beachte, dass diese Einstellung von deinem Seiten-Administrator überschrieben werden kann, was bedeutet, dass alle deine Beiträge standardmäßig "öffentlich" sind (bspw. für das gesamte Internet). + +Wenn du deine Beiträge standardmäßig "öffentlich" haben willst, dann kannst du deine Standardzugriffsrechte auf deiner Einstellungseite ändern. Dort kannst du außerdem festlegen, welchen Gruppen standardmäßig deine Beiträge erhalten oder in welche Gruppe deine neuen Kontakte standardmäßig eingeordnet werden. + +**Fragen der Privatssphäre, die zu beachten sind** + +Diese privaten Gespräche funktionieren am besten, wenn deine Freunde Friendica-Mitglieder sind. So wissen wir, wer sonst noch deine Gespräche sehen kann - niemand, *solange* deine Freunde deine Nachrichten nicht kopieren und an andere verschicken. + +Dies ist eine Vertrauensfrage, die du beachten musst. Keine Software der Welt kann deine Freunde davon abhalten, die privaten Unterhaltungen zu veröffentlichen. Nur eine gute Auswahl deiner Freunde. + +Bei status.net, identi.ca und anderen Netzwerk-Anbietern ist es nicht so gesichert. Du musst **sehr** vorsichtig sein, wenn du Mitglieder anderer Netzwerke in einer deiner Gruppen hast, da es möglich ist, dass deine privaten Nachrichten in einem öffentlichen Stream enden. Wenn du auf die "Kontakt bearbeiten"-Seite einer Person gehst, zeigen wir dir, ob sie Mitglied eines unsicheren Netzwerks ist oder nicht. + +Sobald du einen Post erstellt hast, kannst du die Zugriffsrechte nicht mehr ändern. Innerhalb von Sekunden ist dieser an viele verschiedene Personen verschickt worden - möglicherweise bereits an alle Addressierten. Wenn du versehentlich eine Nachricht erstellt hast und sie zurücknehmen willst, dann ist es das beste, diese zu löschen. Wir senden eine Löschmitteilung an jeden, der deine Nachricht erhalten hat - und das sollte die Nachricht genauso schnell löschen, wie sie zunächst erstellt wurde. In vielen Fällen wird sie in weniger als einer Minute aus dem Internet gelöscht. Nochmals: das gilt für Friendica-Netzwerke. Sobald eine Nachricht an ein anderes Netzwerk geschickt wurde, kann es nicht mehr so schnell gelöscht werden und in manchen Fällen auch gar nicht mehr. + +Wenn du das bisher noch nicht wusstest, dann empfehlen wir dir, deine Freunde dazu zu ermutigen, auch Friendica zu nutzen, da alle diese Privatsphären-Einstellungen innerhalb eines privatsphärenbewussten Netzwerk viel besser funktionieren. Viele andere Netzwerke, mit denen sich Friendica verbinden kann, bieten keine Kontrolle über die Privatsphäre. + + +Profile, Fotos und die Privatsphäre +============================= + +Die dezentralisierte Natur von Friendica (statt eine Webseite zu haben, die alles kontrolliert, gibt es viele Webseiten, die Information austauschen) hat in der Kommunikation mit anderen Seiten einige Konsequenzen. Du solltest dir über einige Dinge bewusst sein, um am besten entscheiden zu können, wie du mit deiner Privatsphäre umgehst. + +**Fotos** + +Fotos privat zu verteilen ist ein Problem. Wir können Fotos nur mit Friendica-Nutzern __privat__ austauschen. Um mit anderen Leuten Fotos zu teilen, müssen wir erkennen, wer sie sind. Wir können die Identität von Friendica-Nutzern prüfen, da es hierfür einen Mechanismus gibt. Deine Freunde anderer Netzwerke werden deine privaten Fotos nicht sehen können, da wir deren Identität nicht überprüfen können. + +Unsere Entwickler arbeiten an einer Lösung, um deinen Freunden den Zugriff zu ermöglichen - unabhängig, zu welchem Netzwerk sie gehören. Wir nehmen hingegen Privatsphäre ernst und agieren nicht wie andere Netzwerke, die __nur so tun__ als ob deine Fotos privat sind, sie aber trotzdem anderen ohne Identitätsprüfung zeigen. + +**Profile** + +Dein Profil und deine "Wall" sollen vielleicht auch von Freunden anderer Netzwerke besucht werden können. Wenn du diese Seiten allerdings für Webbesucher sperrst, die Friendica nicht kennt, kann das auch Freunde anderer Netzwerke blockieren. + +Das kann möglicherweise ungewollte Ergebnisse produzieren, wenn du lange Statusbeiträge z.B. für Twitter oder Facebook schreibst. Wenn Friendica einen Beitrag an diese Netzwerke schickt und nur eine bestimmte Nachrichtenlänge erlaubt ist, dann verkürzen wir diesen und erstellen einen Link, der zum Originalbeitrag führt. Der Originallink führt zurück zu deinem Friendica-Profil. Da Friendica nicht bestätigen kann, um wen es sich handelt, kann es passieren, dass diese Leute den Beitrag nicht komplett lesen können. + +Für Leute, die davon betroffen sind, schlagen wir vor, eine Zusammenfassung in Twitter-Länge zu erstellen mit mehr Details für Freunde, die den ganzen Beitrag sehen können. + +Dein Profil oder deine gesamte Friendica-Seite zu blockieren, hat außerdem ernsthafte Einflüsse auf deine Kommunikation mit StatusNet/identi.ca-Nutzern. Diese Netzwerke kommunizieren mit anderen über öffentliche Protokolle, die nicht authentifiziert werden. Um deine Beiträge zu sehen, müssen diese Netzwerke deine Beiträge als "unbekannte Webbesucher" ansehen. Wenn wir das erlauben, würde es dazu führen, das absolut jeder deine Beiträge sehen. Und du hast Friendica so eingestellt, das nicht zuzulassen. Beachte also, dass das Blockieren von unbekannten Besuchern auch dazu führen kann, dass öffentliche Netzwerke (wie identi.ca) und Newsfeed-Reader wie Google Reader auch geblockt werden. diff --git a/doc/de/Home.md b/doc/de/Home.md new file mode 100644 index 0000000000..26febad5a7 --- /dev/null +++ b/doc/de/Home.md @@ -0,0 +1,39 @@ +Friendica - Dokumentation und Ressourcen +===================================== + +**Inhalte** + +* [Account - Basics](help/Account-Basics) +* [Schnellstart für neue Benutzer](help/guide) +* [Beiträge erstellen](help/Text_editor) +* [Beiträge kommentieren, einordnen und löschen](help/Text_comment) +* [Profile](help/Profiles) +* [Konnektoren (Connectors)](help/Connectors) +* [Freunde finden](help/Making-Friends) +* [Gruppen und Privatsphäre](help/Groups-and-Privacy) +* [Tags und Erwähnungen](help/Tags-and-Mentions) +* [Seiten](help/Pages) +* [Account löschen](help/Remove-Account) +* [Bugs und Probleme](help/Bugs-and-Issues) + +**Technische Dokumentation** + +* [Installation](help/Install) +* [Konfigurationen](help/Settings) +* [Plugins](help/Plugins) +* [Konnektoren (Connectors) installieren (Facebook/Twitter/StatusNet)](help/Installing-Connectors) +* [Nachrichtenfluss](help/Message-Flow) +* [Entwickler](help/Developers) + + +**Externe Ressourcen** + +* [Haupt-Webseite](http://friendica.com) +* [Foren](http://groups.google.com/group/friendica) +* [Entwickler-Foren](http://groups.google.com/group/friendica-dev) +* [Deutsches Friendica-Wiki](http://wiki.toktan.org/doku.php) + +**Über diese Seite** + +* [Seite/Friendica-Version](friendica) + diff --git a/doc/de/Install.md b/doc/de/Install.md new file mode 100644 index 0000000000..9d430eeffa --- /dev/null +++ b/doc/de/Install.md @@ -0,0 +1,89 @@ +Friendica Installation +========== + +* [Zur Startseite der Hilfe](help) + +Wir haben hart daran gearbeitet, um Friendica auf vorgefertigten Hosting-Plattformen zum Laufen zu bringen - solche, auf denen auch Wordpress Blogs und Drupal-Installationen laufen. Aber bedenke, dass Friendica mehr als eine einfache Webanwendung ist. Es handelt sich um ein komplexes Kommunikationssystem, das eher an einen Email-Server erinnert als an einen Webserver. Um die Verfügbarkeit und Performance zu gewährleisten, werden Nachrichten im Hintergrund verschickt und gespeichert, um sie später zu verschicken, wenn eine Webseite gerade nicht erreichbar ist. Diese Funktionalität benötigt ein wenig mehr als die normalen Blogs. Nicht jeder PHP/MySQL-Hosting-Anbieter kann Friendica unterstützen. Viele hingegen können es. Aber **bitte** prüfe die Voraussetzungen deines Servers vor der Installation. + +Wenn dir Fehler während der Installation auffallen, sag uns bitte über das Forum auf http://groups.google.com/group/friendica oder über http://bugs.friendica.com Bescheid. Gib uns bitte so viele Infos zu deinem System, wie du kannst, und beschreibe den Fehler mit allen Details und Fehlermeldungen, so dass wir den Fehler zukünftig verhindern können. Aufgrund der großen Anzahl an verschiedenen Betriebssystemen und PHP-Plattformen haben wir nur geringe Kapazitäten, um deine PHP-Installation zu debuggen oder fehlende Module zu ersetzen, aber wir tun unser Bestes, um allgemeine Code-Fehler zu beheben. + +Bevor du anfängst: suche dir einen Domain- oder Subdomainnamen für deinen Server. Denke ausreichend darüber nach, da ein Wechsel nach der Friendica-Installation derzeit nicht unterstützt wird. Dinge verändern sich und einige deiner Freunde haben möglicherweise Probleme, mit dir zu kommunizieren. Wir planen, diese Einschränkung in einer zukünftigen Version zu beheben. + + +1. Voraussetzungen + - Apache mit einer aktiverten mod-rewrite-Funktion und dem Eintrag "Options All", so dass du die lokale .htaccess-Datei nutzen kannst + - PHP 5.2+. Je neuer, desto besser. Du benötigst 5.3 für die Authentifizierung untereinander. In einer Windows-Umgebung arbeitet die Version 5.2+ möglicherweise nicht, da die Funktion dns_get_record() erst ab Version 5.3 verfügbar ist. + - PHP *Kommandozeilen*-Zugang mit register_argc_argv auf "true" gesetzt in der php.ini-Datei + - curl, gd, mysql und openssl-Erweiterung + - etwas in der Art eines Email-Servers oder eines Gateways wie PHP mail() + - mcrypt (optional; wird für die Server-zu-Server Nachrichtenentschlüsselung benötigt) + - Mysql 5.x + - die Möglichkeit, wiederkehrende Aufgaben mit cron (Linux/Mac) oder "Scheduled Tasks" einzustellen (Windows) [Beachte: andere Optionen sind in Abschnitt 7 dieser Dokumentation zu finden] + - Installation in einer Top-Level-Domain oder Subdomain (ohne eine Verzeichnis/Pfad-Komponente in der URL) wird bevorzugt. Verzeichnispfade sind für diesen Zweck nicht so günstig und wurden auch nicht ausführlich getestet. + + + [Dreamhost.com bietet ein ausreichendes Hosting-Paket mit den nötigen Features zu einem annehmbaren Preis. Wenn dein Hosting-Anbieter keinen Unix-Zugriff erlaubt, kannst du Schwierigkeiten mit der Einrichtung der Webseite haben. + + +2. Entpacke die Friendica-Daten in das Quellverzeichnis (root) des Dokumentenbereichs deines Webservers. + + - Wenn du die Möglichkeit hierzu hast, empfehlen wir dir "git" zu nutzen, um die Daten direkt von der Quelle zu klonen, statt die gepackte .tar- oder .zip-Datei zu nutzen. Das macht die Aktualisierung wesentlich einfacher. Der Linux-Code, mit dem man die Dateien direkt in ein Verzeichnis wie "meinewebseite" kopiert, ist + + `git clone https://github.com/friendica/friendica.git meinewebseite` + + - und dann kannst du die letzten Änderungen immer mit dem folgenden Code holen + + `git pull` + + - Addons installieren + - zunächst solltest du **in** deinem Webseitenordner sein + + `cd meinewebseite` + + - dann kannst du das Addon-Verzeichnis seperat kopieren + + `git clone https://github.com/friendica/friendica-addons.git addon` + + - Um das Addon-Verzeichnis aktuell zu halten, solltest du in diesem Pfad ein "git pull"-Befehl eintragen + + `cd meinewebseite/addon` + + `git pull` + + - Wenn du den Verzeichnispfad auf deinen Webserver kopierst, dann stelle sicher, dass du auch die .htaccess kopierst, da "Punkt"-Dateien oft versteckt sind und normalerweise nicht direkt kopiert werden. + + +3. Erstelle eine leere Datenbank und notiere alle Zugangsdaten (Adresse der Datenbank, Nutzername, Passwort, Datenbankname). + +4. Besuche deine Webseite mit deinem Browser und befolge die Anleitung. Bitte beachte jeden Fehler und korrigiere diese, bevor du fortfährst. + +5. *Wenn* die automatisierte Installation aus irgendeinem Grund fehlschlägt, dann prüfe das Folgende: + + - ".htconfig.php" existiert ... wenn nicht, bearbeite die „htconfig.php“ und ändere die Systemeinstellungen. Benenne sie um in „.htconfig.php" +“ + - die Datenbank beinhaltet Daten. ... wenn nicht, importiere den Inhalt der Datei "database.sql" mit phpmyadmin oder per mysql-Kommandozeile. + +6. Besuche deine Seite an diesem Punkt wieder und registriere deinen persönlichen Account. Alle Registrierungsprobleme sollten automatisch behebbar sein. +Wenn du irgendwelche **kritischen** Fehler zu diesen Zeitpunkt erhalten solltest, deutet das darauf hin, dass die Datenbank nicht korrekt installiert wurde. Du kannst bei Bedarf die Datei .htconfig.php verschieben/umbenennen und die Datenbank leeren (als „Dropping“ bezeichnet), so dass du mit einem sauberen System neu starten kannst. + +7. Erstelle einen Cron job oder einen regelmäßigen Task, um den Poller alle 5-10 Minuten im Hintergrund ablaufen zu lassen. Beispiel: + + `cd /base/directory; /path/to/php include/poller.php` + +Ändere "/base/directory" und "/path/to/php" auf deine Systemvorgaben. + +Wenn du einen Linux-Server nutzt, benutze den Befehl "crontab -e" und ergänze eine Zeile wie die Folgende; angepasst an dein System + +`*/10 * * * * cd /home/myname/mywebsite; /usr/bin/php include/poller.php` + +Du kannst den PHP-Pfad finden, indem du den Befehl „which php“ ausführst. Wenn du Schwierigkeiten mit diesem Schritt hast, kannst du deinen Hosting-Anbieter kontaktieren. Friendica wird nicht korrekt laufen, wenn dieser Schritt nicht erfolgreich abgeschlossen werden kann. + +Alternativ kannst du das Plugin 'poormancron' nutzen, um diesen Schritt durchzuführen, wenn du eine aktuelle Friendica-Version nutzt. Um dies zu machen, musst du die ".htconfig.php" an der Stelle anpassen, die dein Plugin beschreibt. In einer frischen Installation sieht es aus wie: + +`$a->config['system']['addon'] = 'js_upload';` + +Dies setzt voraus, dass das Addon-Modul "js_upload" aktiviert ist. Du kannst auch weitere Addons/Plugins ergänzen. Ändere den Eintrag folgendermaßen ab: + +`$a->config['system']['addon'] = 'js_upload,poormancron';` + +und speichere deine Änderungen. diff --git a/doc/de/Installing-Connectors.md b/doc/de/Installing-Connectors.md new file mode 100644 index 0000000000..80739ace7d --- /dev/null +++ b/doc/de/Installing-Connectors.md @@ -0,0 +1,110 @@ +Konnektoren installieren (Facebook/Twitter/StatusNet) +================================================== + +* [Zur Startseite der Hilfe](help) + +Friendica nutzt Plugins, um die Verbindung zu anderen Netzwerken wie Facebook und Twitter zu gewährleisten. + +Es gibt außerdem ein Plugin, um über einen bestehenden Status.Net-Account diesen Service zu nutzen. Du brauchst dieses Plugin aber nicht, um mit Status.Net-Mitgliedern von Friendica aus zu kommunizieren - es sei denn, du wünschst es, über einen existierenden Account einen Beitrag zu schreiben. + +Alle drei Plugins benötigen einen Account im gewünschten Netzwerk. Zusätzlich musst du (bzw. der Administrator der Seite) einen API-Schlüssel holen, um einen authentifizierten Zugriff zu deinem Friendica-Server herstellen zu lassen. + + +**Seitenkonfiguration** + +Plugins müssen vom Administrator installiert werden, bevor sie genutzt werden können. Dieses kann über das Administrationsmenü erstellt werden. + +Jeder der Konnektoren benötigt zudem einen API-Schlüssel vom Service, der verbunden werden soll. Einige Plugins erlaube es, diese Informationen auf den Administrationsseiten einzustellen, wohingegen andere eine direkte Bearbeitung der Konfigurationsdatei ".htconfig.php" erfordern. Der Weg, um diese Schlüssel zu erhalten, variiert stark, jedoch brauchen fast alle einen bestehenden Account im gewünschten Service. Einmal installiert, können diese Schlüssel von allen Seitennutzern genutzt werden. + +Im Folgenden findest du die Einstellungen für die verschiedenen Services (viele dieser Informationen kommen direkt aus den Quelldateien der Plugins): + + +**Twitter Plugin für Friendica** + +* Author: Tobias Diekershoff +* tobias.diekershoff@gmx.net + +* License:3-clause BSD license + +Konfiguration: +Um dieses Plugin zu nutzen, benötigst du einen OAuth Consumer-Schlüsselpaar (Schlüssel und Geheimnis), das du auf der Seite [https://twitter.com/apps](https://twitter.com/apps) erhalten kannst + +Registriere deine Friendica-Seite als "Client"-Anwendung mit "Read&Write"-Zugriff. Wir benötigen "Twitter als Login" nicht. Sobald du deine Anwendung installiert hast, erhältst du das Schlüsselpaar für deine Seite. + +Trage dieses Schlüsselpaar in deine globale ".htconfig.php"-Datei ein. + +``` +$a->config['twitter']['consumerkey'] = 'your consumer_key here'; +$a->config['twitter']['consumersecret'] = 'your consumer_secret here'; +``` + +Anschließend kann der Nutzer deiner Seite die Twitter-Einstellungen selbst eintragen: "Einstellungen -> Connector Einstellungen". + +Dokumentation: http://diekershoff.homeunix.net/redmine/wiki/friendikaplugin/Twitter_Plugin + + +**StatusNet Plugin für Friendica** + +* Author: Tobias Diekershoff +* tobias.diekershoff@gmx.net + +* License:3-clause BSD license + +Konfiguration + +Wenn das Addon aktiv ist, muss der Nutzer die folgenden Einstellungen vornehmen, um sich mit dem StatusNet-Account seiner Wahl zu verbinden. + +* Die Basis-URL des StatusNet-API; für identi.ca ist es https://identi.ca/api/ +* OAuth Consumer key & Geheimnis + +Um das OAuth-Schlüsselpaar zu erhalten, muss der Nutzer + +(a) seinen Friendica-Admin fragen, ob bereits ein Schlüsselpaar existiert oder +(b) einen Friendica-Server als Anwendung auf dem StatusNet-Server anmelden. + +Dies kann über Einstellungen --> Connections --> "Register an OAuth client application" -> "Register a new application" auf dem StatusNet-Server durchgeführt werden. + +Während der Registrierung des OAuth-Clients ist Folgendes zu beachten: + +* Der Anwendungsname muss auf der StatusNet-Seite einzigartig sein, daher empfehlen wir einen Namen wie "friendica-nnnn", ersetze dabei "nnnn" mit einer frei gewählten Nummer oder deinem Webseitennamen. +* es gibt keine Callback-URL +* Registriere einen Desktop-Client +* stelle Lese- und Schreibrechte ein +* die Quell-URL sollte die URL deines Friendica-Servers sein + +Sobald die benötigten Daten gespeichert sind, musst du deinen Friendica-Account mit StatusNet verbinden. Das kannst du über Einstellungen --> Connector-Einstellungen durchführen. Folge dem "Einloggen mit StatusNet"-Button, erlaube den Zugriff und kopiere den Sicherheitscode in die entsprechende Box. Friendica wird dann versuchen, die abschließende OAuth-Einstellungen über die API zu beziehen. + +Wenn es geklappt hat, kannst du in den Einstellungen festlegen, ob deine öffentlichen Nachrichten automatisch in deinem StatusNet-Account erscheinen soll (achte hierbei auf das kleine Schloss-Symbol im Status-Editor) + +Dokumentation: http://diekershoff.homeunix.net/redmine/wiki/friendikaplugin/StatusNet_Plugin + + +**Installiere den Friendica/Facebook-Konnektor** + +* Registriere einen API-Schlüssel für deine Seite auf [developer.facebook.com](Facebook). + +Hierfür benötigst du einen Facebook-Account und ggf. weitere Authentifizierungen über eine Kreditkarten- oder Mobilfunknummer. + +a. Wir würden uns sehr darüber freuen, wenn du "Friendica" in dem Anwendungsnamen eintragen würdest, um die Bekanntheit des Namens zu erhöhen. Das Friendica-Icon ist im Bildverzeichnis enthalten und kann als Anwendungs-Icon für die Facebook-App genutzt werden. Nutze [images/friendica-16.jpg](images/friendica-16.jpg) für das Icon und [images/friendica-128.jpg](images/friendica-128.jpg) für das Logo. + +b. Die URL sollte deine Seite mit dem abschließenden Schrägstrich sein + +Es **kann** notwendig sein, dass du eine "Privacy"- oder "Terms of service"-URL angeben musst. + +c. Setze nun noch unter "App Domains" die URL auf deineSubdomain.deineDomain.de und bei "Website with Facebook Login" die URL zu deineDomain.de. + +d. Installiere nun das Facebook-Plugin auf deiner Friendica-Seite über "admin/plugins". Du solltest links in der Sidebar einen Facebook-Link unter "Plugin Features" finden. Klicke diesen an. + +e. Gib nun die App-ID und das App-Secret ein, die Facebook dir gegeben hat. Ändere die anderen Daten, wie es gewünscht ist. + +Auf Friendica kann nun jeder Nutzer, der eine Verbindung zu Facebook wünscht, die Seite "Einstellungen -> Connector-Einstellungen" aufrufen und dort "Installiere Facebook-Connector" auswählen. + +Wähle die gewünschten Einstellungen für deine Nutzungs- und Privatsphäreansprüche. + +Hier meldest du dich bei Facebook an und gibst dem Plugin die nötigen Zugriffsrechte, um richtig zu funktionieren. Erlaube dieses. + +Und fertig. Um es abzustellen, gehe wieder auf die Einstellungsseite und auf "Remove Facebook posting". + +Videos und eingebetteter Code werden nicht gepostet, wenn sonst kein anderer Inhalt enthalten ist. Links und Bilder werden in ein Format übertragen, das von der Facebook-API verstanden wird. Lange Texte werden verkürzt und mit einem Link zum Originalbeitrag versehen. + +Facebook-Kontakte können außerdem keine privaten Fotos sehen, da diese nicht richtig authentifiziert werden können, wenn sie deine Seite besuchen. Dieser Fehler wird zukünftig bearbeitet. diff --git a/doc/de/Making-Friends.md b/doc/de/Making-Friends.md new file mode 100644 index 0000000000..e6725228ff --- /dev/null +++ b/doc/de/Making-Friends.md @@ -0,0 +1,47 @@ +Freunde finden +============== + +* [Zur Startseite der Hilfe](help) + +Freundschaft kann in Friendica viele verschiedene Bedeutungen annehmen. Aber lasst es uns einfach halten, du willst einfach mit jemandem befreundet sein. Wie machst du das? + +Der einfachste Weg, um das zu machen, ist es, der Gruppe Neu hier beizutreten. Diese Gruppe ist speziell für Leute, die neu im Friendica-Netzwerk sind. Verbinde dich einfach mit der Gruppe, schreibe auf die "Wall" und lerne neue Leute kennen. Du musst uns nicht einmal direkt "liken" - kommentiere einige Beiträge und andere Leute werden anfangen, dich hinzuzufügen. + +Als Nächstes kannst du dir das Verzeichnis anschauen. Das Verzeichnis ist in zwei Teile aufgeteilt. Wenn du auf den "Verzeichnis"-Button klickst, wirst du zunächst alle Mitglieder deines Servers sehen, die sich dazu entschlossen haben, angezeigt zu werden. Außerdem siehst du dort einen Link zum globalen Verzeichnis. Wenn du dich durch das globale Verzeichnis klickst, siehst du alle Nutzer weltweit auf allen Servern, die sich entschlossen haben, im Verzeichnis zu erscheinen. Du wirst außerdem den Link "Show Community Forums" sehen, welcher dich zu Gruppen, Foren und Fan-Seiten führt. Du verbindest dich mit Personen, Gruppen und Foren auf die gleiche Art, wobei Gruppen und Foren deine Anfrage automatisch annehmen, wohingegen ein Mensch dich erst manuell bestätigen muss. + +*Mit anderen Friendica-Nutzern verbinden* + +Bes‪uche ihr Profil. Direkt unter dem Profilfoto ist das Wort "Verbinden" (bzw. "Connect" in einem englischsprachigem Profil). Klicke drauf und du gelangst zur "Verbinden"-Seite. Dort wirst du nach deiner Identitätsadresse gefragt. Das ist nötig, damit die Seite dein Profil finden kann. + +*Was kommt in die Box?* + +Wenn deine Friendica-Seite "demo.friendica.com" heißt und dein Nutzername/Spitzname auf der Seite "bob" ist, dann wäre es "bob@demo.friendica.com". Wie du siehst, sieht es wie eine Email-Adresse aus. Das ist beabsichtigt, da sich die Leute das so leichter merken können. Du *kannst* auch die URL deiner Startseite eintragen, wie z.B. "http://demo.friendica.com/profile/bob", aber der Email-Adressen-Stil ist einfacher. + +Wenn du die "Verbinden"-Seite bestätigt hast, kommst du zurück zu deiner Seite, um dort die Anfrage zu bestätigen. Wenn du das gemacht hast, können beide Seiten miteinander kommunizieren, um den Prozess abzuschließen (sobald dein neuer Freund die Anfrage bestätigt hat). + +Wenn du bereits die Identitäts-Adresse einer Person kennst, kannst du diese auch direkt in das "Verbinden"-Feld auf deiner "Kontakte"-Seite eintragen. Dies wird dich durch einen ähnlichen Prozess leiten. + + +**Alternative Netzwerke** + +Du kannst deine oder andere Identitäts-Adressen ebenfalls nutzen, um über verschiedene Netzwerke hinweg Freundschaften aufzubauen. Die Liste möglicher Netzwerke steigt immer weiter. Wenn du z.B. "bob" auf identi.ca (eine Status.Net-Seite) kennst, dann kannst du bob@identi.ca auf deiner "Kontakt"-Seite verbinden. (Oder du kannst die URL von Bobs identi.ca-Seite eintragen, wenn du es wünscht). Du kannst auch "teilweise" mit Leuten auf Google Buzz befreundet sein, wenn du deren GMail-Adresse einträgst. Google Buzz unterstützt bisher noch nicht alle Protokolle, die wir für die direkte Kommunikation benötigen, aber es sollte möglich sein, Statusupdates von Friendica zu folgen. Das Gleiche gilt für Twitter- und Diaspora-Accounts. Tatsächlich kannst du jedem und jeder Website folgen, der/die einen Syndication-Feed (RSS/Atom etc.) zur Verfügung stellt. Wenn wir einen Informationsstrom und einen Namen dazu finden, können wir auch versuchen, uns damit zu verbinden. + +Wenn du deine Email-Postfachverbindung auf deiner Einstellungsseite konfiguriert hast, dann kannst du die Email-Adresse jeder Person eintragen, die dir schon eine Nachricht an dein Postfach geschickt hat und bereits in deinem sozialen Stream erscheint. Du kannst diesen Personen außerdem von Friendica aus antworten. + +Leute können sich ebenfalls von anderen Netzwerken aus mit dir befreunden. Ein Freund von dir hat einen identi.ca-Account und kann sich mit dir befreunden, indem er deine Identitäts-Adresse in seine identi.ca-Verbinden-Dialogbox einträgt. Ein ähnlicher Mechanismus ist für Diaspora-Nutzer vorhanden, indem deine Identitäts-Adresse in ihre Suchleiste eingegeben wird. + +Beachte: Manche StatusNet-Versionen benötigen die volle URL deines Profils und funktionieren möglicherweise nicht mit der Identitäts-Adresse. + +Wenn jemand eine Freundschaftsanfrage schickt, erhältst du eine Benachrichtigung. Du musst dann diese Anfrage bestätigen, um die Freundschaftsanfrage abzuschließen. + +Einige Netzwerke erlauben es, Nachrichten zu schicken, ohne befreundet zu sein oder deine Bestätigung zu benötigen. Friendica erlaubt dies in der Standardeinstellung nicht, da es zu Spam führen kann. + +Wenn du eine Freundschaftsanfrage von einem anderen Friendica-Nutzer erhältst, dann hast du die Möglichkeit, diesen als "Fan" oder "Freund" einzutragen. Ein Fan kann sehen, was du schreibst und auch private Kommunikation sehen, die du zu diesen sendest, aber nicht umgekehrt. Als Freund kannst du in beide Richtungen kommunizieren. + +Diaspora nutzt eine andere Terminologie mit der Unterteilung in "mit dir teilen" und "Freund". + +Sobald ihr Freunde geworden seid, dir die Person aber permanent Spam oder sinnlose Informationen schickt, dann kannst du diese "ignorieren" - ohne die Freundschaft komplett zu beenden oder denjenigen zu zeigen, dass du nicht daran interessiert bist, was diese Person sagt. In verschiedener Hinsicht sind diese Personen wie "Fans", aber sie wissen es nicht. Sie denken, sie sind als Freunde eingetragen. + +Du kannst auch eine Person "blocken". Das blockt die komplette Kommunikation mit dieser Person. Sie können zwar weiterhin öffentliche Beiträge sehen, wie auch jeder andere in der Welt, allerdings können sie nicht direkt mit dir kommunizieren. + +Du kannst Freunde löschen, egal wie der Freundschaftsstatus ist, was dazu führt, dass alles, was mit dieser Person verbunden ist, von deiner Webseite gelöscht wird. diff --git a/doc/de/Message-Flow.md b/doc/de/Message-Flow.md new file mode 100644 index 0000000000..add6507ea4 --- /dev/null +++ b/doc/de/Message-Flow.md @@ -0,0 +1,43 @@ +Friendica Nachrichtenfluss +============== + +* [Zur Startseite der Hilfe](help) + +Diese Seite soll einige Infos darüber dokumentieren, wie Nachrichten innerhalb von Friendica von einer Person zur anderen übertragen werden. Es gibt verschiedene Pfade, die verschiedene Protokolle und Nachrichtenformate nutzen. + +Diejenigen, die den Nachrichtenfluss genauer verstehen wollen, sollten sich mindestens mit dem DFRN-Protokoll (http://dfrn.org/dfrn.pdf) und den Elementen zur Nachrichtenverarbeitung des OStatus Stack informieren (salmon und Pubsubhubbub). + +Der Großteil der Nachrichtenverarbeitung nutzt die Datei include/items.php, welche Funktionen für verschiedene Feed-bezogene Import-/Exportaktivitäten liefert. + +Wenn eine Nachricht veröffentlicht wird, werden alle Übermittlungen an alle Netzwerke mit include/notifier.php durchgeführt, welche entscheidet, wie und an wen die Nachricht geliefert wird. Diese Datei bindet dabei die lokale Bearbeitung aller Übertragungen ein inkl. dfrn-notify. + +mod/dfrn_notify.php handhabt die Rückmeldung (remote side) von dfrn-notify. + +Lokale Feeds werden durch mod/dfrn_poll.php generiert - was ebenfalls die Rückmeldung (remote side) von dfrn-notify handhabt. + +Salmon-Benachrichtigungen kommen via mod/salmon.php an. + +PuSh-Feeds (pubsubhubbub) kommen via mod/pubsub.php an. + +DFRN-poll Feed-Imports kommen via include/poller.php als geplanter Task an, das implementiert die lokale Bearbeitung (local side) des DFRN-Protokolls. + + +Szenario #1. Bob schreibt eine öffentliche Statusnachricht + +Dies ist eine öffentliche Nachricht ohne begrenzte Nutzerfreigabe, so dass keine private Übertragung notwendig ist. Es gibt zwei Wege, die genutzt werden können - als bbcode an DFRN-Clients oder als durch den Server konvertierten HTML-Code (mit PuSH; pubsubhubbub). Wenn ein PuSH-Hub einsatzfähig ist, nutzen DFRN-Poll-Clients vorrangig die Informationen, die durch den PuSH-Kanal kommen. Sie fallen zurück auf eine tägliche Abfrage, wenn der Hub Übertragungsschwierigkeiten hat (das kann vorkommen, wenn der standardmäßige Google-Referenzhub genutzt wird). Wenn kein spezifizierter Hub oder Hubs ausgewählt sind, werden DFRN-Clients in einer pro Kontakt konfigurierbaren Rate mit bis zu 5-Minuten-Intervallen abfragen. Feeds, die via DFRN-Poll abgerufen werden, sind bbcode und können auch private Unterhaltungen enthalten, die vom Poller auf ihre Zugriffsrechte hin geprüft werden. + +Szenario #2. Jack antwortet auf Bobs öffentliche Nachricht. Jack ist im Friendica/DFRN-Netzwerk. + +Jack nutzt dfrn-notify, um eine direkte Antwort an Bob zu schicken. Bob erstellt dann einen Feed der Unterhaltung und sendet diesen an jeden, der an der Unterhaltung beteiligt ist und dfrn-notify nutzt. Die PuSH-Hubs werden darüber informiert, dass neuer Inhalt verfügbar ist. Der/die Hub/s erhalten dann die neuesten Feeds und übertragen diese an alle Hub-Teilnehmer (die auch zu verschiedenen Netzwerken gehören können). + +Szenario #3. Mary antwortet auf Bobs öffentliche Nachricht. Mary ist im Friendica/DFRN-Netzwerk. + +Mary nutzt dfrn-notify, um eine direkte Antwort an Bob zu schicken. Bob erstellt dann einen Feed der Unterhaltung und sendet diesen an jeden, der an der Unterhaltung beteiligt ist (mit Ausnahme von Bob selbst; die Unterhaltung wird nun an Jack und Mary geschickt). Die Nachrichten werden mit dfrn-notify übertragen. PuSH-Hubs werden darüber informiert, dass neuer Inhalt verfügbar ist. Der/die Hub/s erhalten dann die neuesten Feeds und übertragen sie an alle Hub-Teilnehmer (die auch zu verschiedenen Netzwerken gehören können). + +Szenario #4. William antwortet auf Bobs öffentliche Nachricht. William ist in einem OStatus-Netzwerk. + +William nutzt salmon, um Bob über seine Antwort zu benachrichtigen. Der Inhalt ist HTML-Code, der in das Salmon Magic Envelope eingebettet ist. Bob erstellt dann einen Feed der Unterhaltung und sendet es an alle Friendica-Nutzer, die an der Unterhaltung beteiligt sind und dfrn-notify nutzen (mit Ausnahme von William selbst; die Unterhaltung wird an Jack und Mary weitergeleitet). PuSH-Hubs werden darüber informiert, dass neuer Inhalt verfügbar ist. Der/die Hub/s erhalten dann die neuesten Feeds und übertragen sie an alle Hub-Teilnehmer (die auch zu verschiedenen Netzwerken gehören können). + +Szenario #5. Bob schreibt eine private Nachricht an Mary und Jack. + +Die Nachricht wird sofort an Mary und Jack mit Hilfe von dfrn_notify geschickt. Öffentliche Hubs werden nicht benachrichtigt. Im Falle eines Timeouts wird eine erneute Verarbeitung angestoßen. Antworten folgen dem gleichen Nachrichtenfluss wie öffentliche Antworten, allerdings werden die Hubs nicht darüber informiert, wodurch die Nachrichten niemals in öffentliche Feeds gelangen. Die komplette Unterhaltung ist nur für Mary und Jack in ihren durch dfrn-poll personalisierten Feeds verfügbar (und für niemanden sonst). diff --git a/doc/de/Pages.md b/doc/de/Pages.md new file mode 100644 index 0000000000..af6395b78d --- /dev/null +++ b/doc/de/Pages.md @@ -0,0 +1,35 @@ +Seiten +===== + +* [Zur Startseite der Hilfe](help) + + +Friendica lässt dich auch Foren und/oder Prominenten-Seiten erstellen. + +Jede Seite in Friendica hat einen einzigartigen Spitznamen. Das gilt für alle Seiten, unabhängig davon, ob es sich um normale Profile oder Forenseite handelt. + +Das Erste, was du machen musst, um eine neue Seite zu kreieren ist, einen neuen Account zu erstellen. Bitte beachte, dass der Seitenadministrator die Registrierung neuer Accounts sperren oder an Bedingungen knüpfen kann. + +Wenn du einen zweiten Account in einem System erstellst und die gleiche Email-Adresse oder den gleichen OpenID-Account nutzt, kannst du dich zukünftig nur noch mit deinem Spitznamen anmelden. + +Gehe im neuen Account auf die "Einstellungs"-Seite und dort am Ende der Seite auf "Erweiterte Konto-/Seitentyp-Einstellungen". Normalerweise nutzt du "Normales Konto" für einen normalen, persönlichen Account. Das ist die Standardeinstellung. Gr‬uppenseiten bieten die Möglichkeit, Leute als Freund/Fan ohne Kontaktbestätigung zuzulassen. + +Die Auswahl der Einstellung, die du wählst, hängt davon ab, wie du mit anderen Leuten auf deiner Seite interagieren willst. Die "Marktschreier"-Einstellung (Soapbox) lässt den Seitenbesitzer die gesamte Kommunikation kontrollieren. Alles was du schreibst, geht an alle Seitennutzer, aber es gibt keine Möglichkeit, zu interagieren. Diese Seite wird normalerweise für Ankündigungen oder die Kommunikation von Gemeinschaften genutzt. + +Die normalste Einstellung ist das "Forum-/Promi-Konto". Diese erstellt eine Gruppenseite, in der alle Mitglieder frei miteinander interagieren können. Der "Automatische Freunde Seite"-Account ist typischerweise für persönliche Profile, bei denen du alle Freundschaftsanfragen automatisch bestätigen willst. + + +**Multiple Seiten verwalten** + +Wir schlagen vor, dass du eine Gruppenseite mit der gleichen Email-Adresse und dem gleichen Passwort wie bei deinem normalen Account nutzt. Wenn du das machst, findest du einen neuen "Verwalten"-Link im Hauptmenü, das dir hilft, einfach zwischen den Identitäten zu wechseln. Du musst das nicht machen, die Alternative ist allerdings, dich immer wieder aus- und wieder einzuloggen. Und das kann umständlich sein, wenn du mehrere verschiedene Seiten/Identitäten verwaltest. + +Du kannst ebenso jemanden wählen, der deine Seite verwaltet. Mach das, indem du die [Delegierungs-Setup-Seite](delegate) besuchst. Dort wird dir eine Liste an "Potentiellen Bevollmächtigen" angezeigt. Die Auswahl einer oder mehrerer Personen gibt diesen die Möglichkeit, deine Seite zu verwalten. Sie können Kontakte, Profile und alle Inhalte deines Accounts/deiner Seite bearbeiten. Bitte nutze diese Einstellung mit Vorsicht. Delegierte haben jedoch keine Möglichkeit, grundlegende Account-Einstellungen wie das Passwort oder den Seitentypen zu ändern bzw. den Account zu löschen. + + +**Beiträge auf Community-Seiten** + +Wenn du Mitglied einer Community-Seite/-Forums bist, kannst du die Seite in einem Beitrag hinzufügen/erwähnen, wenn du den @-Tag nutzt. Zum Beispiel würde @Fahrrad deinen Beitrag neben den sonst ausgewählten Nutzern an alle Nutzer schicken, die in der Gruppe "Fahrrad" sind. Wenn dein Beitrag privat ist, musst du diese Gruppe explizit in den Zugriffsrechten des Beitrags auswählen **und** sie mit dem @-Tag erwähnen (was den Beitrag auf die Gruppenmitglieder erweitert). + +Du kannst außerdem via "Wall zu Wall" einen Beitrag auf der Community-Seite bzw. in dem Community-Forum erstellen. + +Kommentare, die du an eine Community-Seite schickst, werden an den Originalbeitrag weitergeleitet. Das Forum bzw. die Gruppe mit dem @-Tag zu erwähnen, leitet den Beitrag nicht weiter, da die Verteilung des Beitrages komplett vom Original-Beitragsschreiber kontrolliert wird. diff --git a/doc/de/Plugins.md b/doc/de/Plugins.md new file mode 100644 index 0000000000..9fa67bd7b7 --- /dev/null +++ b/doc/de/Plugins.md @@ -0,0 +1,343 @@ +**Friendica Addon/Plugin-Entwicklung** +============== + +* [Zur Startseite der Hilfe](help) + +Bitte schau dir das Beispiel-Addon "randplace" für ein funktionierendes Beispiel für manche der hier aufgeführten Funktionen an. Das Facebook-Addon bietet ein Beispiel dafür, die "addon"- und "module"-Funktion gemeinsam zu integrieren. Addons arbeiten, indem sie Event Hooks abfangen. Module arbeiten, indem bestimmte Seitenanfragen (durch den URL-Pfad) abgefangen werden + +Plugin-Namen können keine Leerstellen oder andere Interpunktionen enthalten und werden als Datei- und Funktionsnamen genutzt. Du kannst einen lesbaren Namen im Kommentarblock eintragen. Jedes Addon muss beides beinhalten - eine Installations- und eine Deinstallationsfunktion, die auf dem Addon-/Plugin-Namen basieren; z.B. "plugin1name_install()". Diese beiden Funktionen haben keine Argumente und sind dafür verantwortlich, Event Hooks zu registrieren und abzumelden (unregistering), die dein Plugin benötigt. Die Installations- und Deinstallationsfunktionfunktionen werden auch ausgeführt (z.B. neu installiert), wenn sich das Plugin nach der Installation ändert - somit sollte deine Deinstallationsfunktion keine Daten zerstört und deine Installationsfunktion sollte bestehende Daten berücksichtigen. Zukünftige Extensions werden möglicherweise "Setup" und "Entfernen" anbieten. + +Plugins sollten einen Kommentarblock mit den folgenden vier Parametern enthalten: + + /* + * Name: My Great Plugin + * Description: This is what my plugin does. It's really cool + * Version: 1.0 + * Author: John Q. Public + */ + +Registriere deine Plugin-Hooks während der Installation. + + register_hook($hookname, $file, $function); + +$hookname ist ein String und entspricht einem bekannten Friendica-Hook. + +$file steht für den Pfadnamen, der relativ zum Top-Level-Friendicaverzeichnis liegt. Das *sollte* "addon/plugin_name/plugin_name.php' sein. + +$function ist ein String und der Name der Funktion, die ausgeführt wird, wenn der Hook aufgerufen wird. + +Deine Hook-Callback-Funktion wird mit mindestens einem und bis zu zwei Argumenten aufgerufen + + function myhook_function(&$a, &$b) { + + } + +Wenn du Änderungen an den aufgerufenen Daten vornehmen willst, musst du diese als Referenzvariable (mit "&") während der Funktionsdeklaration deklarieren. + +$a ist die Friendica "App"-Klasse, die eine Menge an Informationen über den aktuellen Friendica-Status beinhaltet, u.a. welche Module genutzt werden, Konfigurationsinformationen, Inhalte der Seite zum Zeitpunkt des Hook-Aufrufs. Es ist empfohlen, diese Funktion "$a" zu nennen, um seine Nutzung an den Gebrauch an anderer Stelle anzugleichen. + +$b kann frei benannt werden. Diese Information ist speziell auf den Hook bezogen, der aktuell bearbeitet wird, und beinhaltet normalerweise Daten, die du sofort nutzen, anzeigen oder bearbeiten kannst. Achte darauf, diese mit "&" zu deklarieren, wenn du sie bearbeiten willst. + + +**Module** + +Plugins/Addons können auch als "Module" agieren und alle Seitenanfragen für eine bestimte URL abfangen. Um ein Plugin als Modul zu nutzen, ist es nötig, die Funktion "plugin_name_module()" zu definieren, die keine Argumente benötigt und nichts weiter machen muss. + +Wenn diese Funktion existiert, wirst du nun alle Seitenanfragen für "http://my.web.site/plugin_name" erhalten - mit allen URL-Komponenten als zusätzliche Argumente. Diese werden in ein Array $a->argv geparst und stimmen mit $a->argc überein, wobei sie die Anzahl der URL-Komponenten abbilden. So würde http://my.web.site/plugin/arg1/arg2 nach einem Modul "plugin" suchen und seiner Modulfunktion die $a-App-Strukur übergeben (dies ist für viele Komponenten verfügbar). Das umfasst: + + $a->argc = 3 + $a->argv = array(0 => 'plugin', 1 => 'arg1', 2 => 'arg2'); + +Deine Modulfunktionen umfassen oft die Funktion plugin_name_content(&$a), welche den Seiteninhalt definiert und zurückgibt. Sie können auch plugin_name_post(&$a) umfassen, welches vor der content-Funktion aufgerufen wird und normalerweise die Resultate der POST-Formulare handhabt. Du kannst ebenso plugin_name_init(&$a) nutzen, was oft frühzeitig aufgerufen wird und das Modul initialisert. + + +**Derzeitige Hooks:** + +**'authenticate'** - wird aufgerufen, wenn sich der User einloggt. + $b ist ein Array + 'username' => der übertragene Nutzername + 'password' => das übertragene Passwort + 'authenticated' => setze das auf einen anderen Wert als "0", damit der User sich authentifiziert + 'user_record' => die erfolgreiche Authentifizierung muss auch einen gültigen Nutzereintrag aus der Datenbank zurückgeben + +**'logged_in'** - wird aufgerufen, sobald ein Nutzer sich erfolgreich angemeldet hat. + $b beinhaltet den $a->Nutzer-Array + + +**'display_item'** - wird aufgerufen, wenn ein Beitrag für die Anzeige formatiert wird. + $b ist ein Array + 'item' => Die Item-Details (Array), die von der Datenbank ausgegeben werden + 'output' => Die HTML-Ausgabe (String) des Items, bevor es zur Seite hinzugefügt wird + +**'post_local'** - wird aufgerufen, wenn der Statusbeitrag oder ein Kommentar im lokalen System eingetragen wird. + $b ist das Item-Array der Information, die in der Datenbank hinterlegt wird. + {Bitte beachte: der Seiteninhalt ist bbcode - nicht HTML) + +**'post_local_end'** - wird aufgerufen, wenn ein lokaler Statusbeitrag oder Kommentar im lokalen System gespeichert wird. + $b ist das Item-Array einer Information, die gerade in der Datenbank gespeichert wurden. + {Bitte beachte: der Seiteninhalt ist bbcode - nicht HTML) + +**'post_remote'** - wird aufgerufen, wenn ein Beitrag aus einer anderen Quelle empfangen wird. Dies kann auch genutzt werden, um lokale Aktivitäten oder systemgenerierte Nachrichten zu veröffentlichen/posten. + $b ist das Item-Array einer Information, die in der Datenbank und im Item gespeichert ist. + {Bitte beachte: der Seiteninhalt ist bbcode - nicht HTML) + +**'settings_form'** - wird aufgerufen, wenn die HTML-Ausgabe für die Einstellungsseite generiert wird. + $b ist die HTML-Ausgabe (String) der Einstellungsseite vor dem finalen ""-Tag. + +**'settings_post'** - wird aufgerufen, wenn die Einstellungsseiten geladen werden. + $b ist der $_POST-Array + +**'plugin_settings'** - wird aufgerufen, wenn die HTML-Ausgabe der Addon-Einstellungsseite generiert wird. + $b ist die HTML-Ausgabe (String) der Addon-Einstellungsseite vor dem finalen ""-Tag. + +**'plugin_settings_post'** - wird aufgerufen, wenn die Addon-Einstellungsseite geladen wird. + $b ist der $_POST-Array + +**'profile_post'** - wird aufgerufen, wenn die Profilseite angezeigt wird. + $b ist der $_POST-Array + +**'profile_edit'** - wird aufgerufen, bevor die Profil-Bearbeitungsseite angezeigt wird. + $b ist ein Array + 'profile' => Profileintrag (Array) aus der Datenbank + 'entry' => die HTML-Ausgabe (string) des generierten Eintrags + +**'profile_advanced'** - wird aufgerufen, wenn die HTML-Ausgabe für das "Advanced profile" generiert wird; stimmt mit dem "Profil"-Tab auf der Profilseite der Nutzer überein. + $b ist die HTML-Ausgabe (String) des erstellten Profils + (Die Details des Profil-Arrays sind in $a->profile) + +**'directory_item'** - wird von der Verzeichnisseite aufgerufen, wenn ein Item für die Anzeige formatiert wird. + $b ist ein Array + 'contact' => Kontakteintrag (Array) einer Person aus der Datenbank + 'entry' => die HTML-Ausgabe (String) des generierten Eintrags + +**'profile_sidebar_enter'** - wird aufgerufen, bevor die Sidebar "Kurzprofil" einer Seite erstellt wird. + $b ist der Profil-Array einer Person + +**'profile_sidebar'** - wird aufgerufen, wenn die Sidebar "Kurzprofil" einer Seite erstellt wird. + $b ist ein Array + 'profile' => Kontakteintrag (Array) einer Person aus der Datenbank + 'entry' => die HTML-Ausgabe (String) des generierten Eintrags + +**'contact_block_end'** - wird aufgerufen, wenn der Block "Kontakte/Freunde" der Profil-Sidebar komplett formatiert wurde. + $b ist ein Array + 'contacts' => Array von "contacts" + 'output' => die HTML-Ausgabe (String) des Kontaktblocks + +**'bbcode'** - wird während der Umwandlung von bbcode auf HTML aufgerufen. + $b ist der konvertierte Text (String) + +**'html2bbcode'** - wird während der Umwandlung von HTML zu bbcode aufgerufen (z.B. bei Nachrichtenbeiträgen). + $b ist der konvertierte Text (String) + +**'page_header'** - wird aufgerufen, nachdem der Bereich der Seitennavigation geladen wurde. + $b ist die HTML-Ausgabe (String) der "nav"-Region + +**'personal_xrd'** - wird aufgerufen, bevor die Ausgabe der persönlichen XRD-Datei erzeugt wird. + $b ist ein Array + 'user' => die hinterlegten Einträge der Person + 'xml' => die komplette XML-Datei die ausgegeben wird + +**'home_content'** - wird aufgerufen, bevor die Ausgabe des Homepage-Inhalts erstellt wird; wird nicht eingeloggten Nutzern angezeigt. + $b ist die HTML-Ausgabe (String) der Auswahlregion + +**'contact_edit'** - wird aufgerufen, wenn die Kontaktdetails vom Nutzer auf der "Kontakte"-Seite bearbeitet werden. + $b ist ein Array + 'contact' => Kontakteintrag (Array) des abgezielten Kontakts + 'output' => die HTML-Ausgabe (String) der "Kontakt bearbeiten"-Seite + +**'contact_edit_post'** - wird aufgerufen, wenn die "Kontakt bearbeiten"-Seite ausgegeben wird. + $b ist der $_POST-Array + +**'init_1'** - wird aufgerufen, kurz nachdem die Datenbank vor Beginn der Sitzung geöffnet wird. + $b wird nicht genutzt + +**'page_end'** - wird aufgerufen, nachdem die Funktion des HTML-Inhalts komplett abgeschlossen ist. + $b ist die HTML-Ausgabe (String) vom Inhalt-"div" + +**'avatar_lookup'** - wird aufgerufen, wenn der Avatar geladen wird. + $b ist ein Array + 'size' => Größe des Avatars, der geladen wird + 'email' => Email-Adresse, um nach dem Avatar zu suchen + 'url' => generierte URL (String) des Avatars + + +Eine komplette Liste aller Hook-Callbacks mit den zugehörigen Dateien (am 14-Feb-2012 generiert): Bitte schau in die Quellcodes für Details zu Hooks, die oben nicht dokumentiert sind. + +boot.php: call_hooks('login_hook',$o); + +boot.php: call_hooks('profile_sidebar_enter', $profile); + +boot.php: call_hooks('profile_sidebar', $arr); + +boot.php: call_hooks("proc_run", $arr); + +include/contact_selectors.php: call_hooks('network_to_name', $nets); + +include/api.php: call_hooks('logged_in', $a->user); + +include/api.php: call_hooks('logged_in', $a->user); + +include/queue.php: call_hooks('queue_predeliver', $a, $r); + +include/queue.php: call_hooks('queue_deliver', $a, $params); + +include/text.php: call_hooks('contact_block_end', $arr); + +include/text.php: call_hooks('smilie', $s); + +include/text.php: call_hooks('prepare_body_init', $item); + +include/text.php: call_hooks('prepare_body', $prep_arr); + +include/text.php: call_hooks('prepare_body_final', $prep_arr); + +include/nav.php: call_hooks('page_header', $a->page['nav']); + +include/auth.php: call_hooks('authenticate', $addon_auth); + +include/bbcode.php: call_hooks('bbcode',$Text); + +include/oauth.php: call_hooks('logged_in', $a->user); + +include/acl_selectors.php: call_hooks($a->module . '_pre_' . $selname, $arr); + +include/acl_selectors.php: call_hooks($a->module . '_post_' . $selname, $o); + +include/acl_selectors.php: call_hooks('contact_select_options', $x); + +include/acl_selectors.php: call_hooks($a->module . '_pre_' . $selname, $arr); + +include/acl_selectors.php: call_hooks($a->module . '_post_' . $selname, $o); + +include/acl_selectors.php: call_hooks($a->module . '_pre_' . $selname, $arr); + +include/acl_selectors.php: call_hooks($a->module . '_post_' . $selname, $o); + +include/notifier.php: call_hooks('notifier_normal',$target_item); + +include/notifier.php: call_hooks('notifier_end',$target_item); + +include/items.php: call_hooks('atom_feed', $atom); + +include/items.php: call_hooks('atom_feed_end', $atom); + +include/items.php: call_hooks('atom_feed_end', $atom); + +include/items.php: call_hooks('parse_atom', $arr); + +include/items.php: call_hooks('post_remote',$arr); + +include/items.php: call_hooks('atom_author', $o); + +include/items.php: call_hooks('atom_entry', $o); + +include/bb2diaspora.php: call_hooks('bb2diaspora',$Text); + +include/cronhooks.php: call_hooks('cron', $d); + +include/security.php: call_hooks('logged_in', $a->user); + +include/html2bbcode.php: call_hooks('html2bbcode', $text); + +include/Contact.php: call_hooks('remove_user',$r[0]); + +include/Contact.php: call_hooks('contact_photo_menu', $args); + +include/conversation.php: call_hooks('conversation_start',$cb); + +include/conversation.php: call_hooks('render_location',$locate); + +include/conversation.php: call_hooks('display_item', $arr); + +include/conversation.php: call_hooks('render_location',$locate); + +include/conversation.php: call_hooks('display_item', $arr); + +include/conversation.php: call_hooks('item_photo_menu', $args); + +include/conversation.php: call_hooks('jot_tool', $jotplugins); + +include/conversation.php: call_hooks('jot_networks', $jotnets); + +include/plugin.php: if(! function_exists('call_hooks')) { + +include/plugin.php:function call_hooks($name, &$data = null) { + +index.php: call_hooks('init_1'); + +index.php: call_hooks('app_menu', $arr); + +index.php: call_hooks('page_end', $a->page['content']); + +mod/photos.php: call_hooks('photo_post_init', $_POST); + +mod/photos.php: call_hooks('photo_post_file',$ret); + +mod/photos.php: call_hooks('photo_post_end',$foo); + +mod/photos.php: call_hooks('photo_post_end',$foo); + +mod/photos.php: call_hooks('photo_post_end',$foo); + +mod/photos.php: call_hooks('photo_post_end',intval($item_id)); + +mod/photos.php: call_hooks('photo_upload_form',$ret); + +mod/friendica.php: call_hooks('about_hook', $o); + +mod/editpost.php: call_hooks('jot_tool', $jotplugins); + +mod/editpost.php: call_hooks('jot_networks', $jotnets); + +mod/parse_url.php: call_hooks('parse_link', $arr); + +mod/home.php: call_hooks('home_init',$ret); + +mod/home.php: call_hooks("home_content",$o); + +mod/contacts.php: call_hooks('contact_edit_post', $_POST); + +mod/contacts.php: call_hooks('contact_edit', $arr); + +mod/settings.php: call_hooks('plugin_settings_post', $_POST); + +mod/settings.php: call_hooks('connector_settings_post', $_POST); + +mod/settings.php: call_hooks('settings_post', $_POST); + +mod/settings.php: call_hooks('plugin_settings', $settings_addons); + +mod/settings.php: call_hooks('connector_settings', $settings_connectors); + +mod/settings.php: call_hooks('settings_form',$o); + +mod/register.php: call_hooks('register_account', $newuid); + +mod/like.php: call_hooks('post_local_end', $arr); + +mod/xrd.php: call_hooks('personal_xrd', $arr); + +mod/item.php: call_hooks('post_local_start', $_REQUEST); + +mod/item.php: call_hooks('post_local',$datarray); + +mod/item.php: call_hooks('post_local_end', $datarray); + +mod/profile.php: call_hooks('profile_advanced',$o); + +mod/profiles.php: call_hooks('profile_post', $_POST); + +mod/profiles.php: call_hooks('profile_edit', $arr); + +mod/tagger.php: call_hooks('post_local_end', $arr); + +mod/cb.php: call_hooks('cb_init'); + +mod/cb.php: call_hooks('cb_post', $_POST); + +mod/cb.php: call_hooks('cb_afterpost'); + +mod/cb.php: call_hooks('cb_content', $o); + +mod/directory.php: call_hooks('directory_item', $arr); + diff --git a/doc/de/Profiles.md b/doc/de/Profiles.md new file mode 100644 index 0000000000..0da6476d83 --- /dev/null +++ b/doc/de/Profiles.md @@ -0,0 +1,47 @@ +Profile +======== + +* [Zur Startseite der Hilfe](help) + +Mit Friendica kann eine unbegrenzte Anzahl an Profilen angelegt werden. Du kannst verschiedene Profile nutzen, um verschiedenen Gruppen verschiedene Seiten von dir zu zeigen. + +Du hast immer ein Profil, das als dein "Standard"- (default) oder "öffentliches" (public) Profil angelegt ist. Dieses Profil ist immer für die Öffentlichkeit zugänglich und kann nicht versteckt werden (hier mag es einige wenige Ausnahmen auf privaten oder getrennten Seiten geben). Du kannst und solltest die Informationen, die du in deinem öffentlichen Profil veröffentlichst, begrenzen. + +Das bedeutet, dass du folgende Informationen in dein öffentlichen Profil eintragen solltest, wenn du willst, dass Freunde dich finden können ... + +* Dein richtiger Name +* Ein Foto von **dir** +* Dein geographischer Standort; zumindest das Land, in dem du lebst. + +Ohne diese Basisinformationen kannst du hier sehr einsam sein. Die meisten Leute, auch deine besten Freunde, werden nicht versuchen, einen Account mit Spitznamen und ohne Foto zu verbinden. + +Wenn du außerdem Leute mit gleichen Interessen treffen willst, dann nimm dir etwas Zeit und trage einige Stichworte ein. Zum Beispiel etwas wie "Musik, Linux, Photographie" oder andere Dinge. Du kannst so viele Stichworte eintragen, wie du willst. + +Dein "Standard-" oder "öffentliches" Profil wird außerdem Kontakten in anderen Netzwerken gezeigt, auch wenn sie nicht die Möglichkeit haben, die privaten Profile einzusehen. Nur Mitglieder des Friendica-Netzwerks können alternative oder private Profile sehen. + +Um ein alternatives Profil zu erstellen, gehe auf "Profil verwalten/editieren". Du kannst entweder ein bestehendes Profil bearbeiten, das Foto ändern, oder ein neues Profil erstellen. Du kannst ebenfalls einen Klon eines bestehenden Profils erstellen, falls du nur einige wenige Einstellungen ändern, aber nicht alle Daten noch mal eingeben willst. + +Um bestimmten Personen ein Profil zuzuweisen, wähle die Person über "Kontakte" und klicke auf das Bearbeiten-Symbol (Stift). Du wirst ein Auswahlmenü mit verschiedenen vorhandenen Profilen angezeigt bekommen. Wenn diese Auswahl nicht angezeigt wird, dann ist die Person in einem nicht unterstützten Netzwerk und kann dadurch auch kein Profil zugewiesen bekommen. + +Wenn eine befreundete Person auf den "magischen Profillink" klickt, sieht sie das private Profil, das du dieser Person zugewiesen hast. Wenn sie nicht eingeloggt ist oder das Profil von woanders angeschaut wird, wird nur das öffentliche Profil angezeigt. + +Ein "magischer Profillink" erscheint, wenn man mit der Maus über den Kontaktnamen oder das Foto geht. Der Cursor wird zur Hand und auf dem Bild erscheint ein Pfeil, der nach unten zeigt. Dieser "magische Cursor" zeigt an, dass du ein spezielles Profil angezeigt bekommst, das nur für Freunde, aber nicht für die Öffentlichkeit sichtbar ist. + +Du wirst außerdem möglicherweise entdecken (vorausgesetzt, du hast die nötigen Zugriffsrechte), dass du direkt auf die Seite einer anderen Person schreiben kannst (oft wird diese Beitragsart "wall-to-wall" genannt). Ebenso kannst du die Möglichkeit haben, direkt Beiträge zu kommentieren, während du die Seite der anderen Person besuchst. + +Es gibt zwei Einstellungen, welche erlauben, dein Profil ins Verzeichnis einzutragen, so dass du von anderen Personen gefunden werden kannst. Du kannst diese Einstellungen auf deiner "Einstellungen"-Seite ändern. Die eine Einstellung erlaubt dir, dein Profil im Verzeichnis dieses Servers zu veröffentlichen. Die zweite Option erlaubt es dir, dich in das globale Friendica-Verzeichnis einzutragen. Dies ist ein riesiges Verzeichnis, dass alle Personen von vielen Friendica-Installationen weltweit umfasst. + +Wenn du für andere nicht sichtbar sein willst, dann kannst du dein Profil einfach unveröffentlicht lassen. + +Außerdem hast du möglicherweise mehrere Profile, aber nur ein Profilfoto. Dies ist beabsichtigt. In frühen Tests haben wir mit verschiedenen Fotos für jedes Profil experimentiert und herausgefunden, dass es sehr verwirrend für die Nutzer ist. Sie sehen möglicherweise je nach Profil, Seite oder Unterhaltung verschiedene Fotos und merken, dass es unterschiedliche Profile gibt, die sie nicht einsehen können. + +(Du kannst aber die Rich-Text-Infoboxen in deinem Profil nutzen und dort weitere Bilder in das Feld "Erzähle uns ein bisschen von dir …" einfügen.) + + +**Schlüsselwörter und Verzeichnissuche** + +Auf der Verzeichnisseite willst du vielleicht nach Personen deines Servers suchen, die ihre Profile veröffentlicht haben. Die Suche richtet sich normalerweise nach deinem Spitznamen oder Teilen deines richtigen Namens. Darüber hinaus wird dieses Feld auch andere Felder deines Profils wie Geschlecht, Ort, "über mich", Arbeit und Bildung finden. Du kannst zudem auch "Schlüsselwörter" in dein Standardprofil eintragen, so dass dich andere Personen über deine Interessen finden können. Du hast zwei Schlüsselwortarten zur Auswahl - öffentlich und privat. Private Schlüsselwörter werden *nicht* jedem angezeigt. Du kannst diese Schlüsselwörter nutzen, um andere Personen zu finden, die ebenfalls in einer bestimmten Gruppe sind oder z.B. das Fischen mögen, ohne dass es jeder in einem öffentlichen Profil sieht. Öffentliche Schlüsselwörter werden auf der "Kontaktvorschläge"-Seite genutzt. Auch wenn die Schlüsselwörter hier nicht direkt angezeigt werden, kann es trotzdem sein, dass diese im HTML-Code der Seite gesehen werden könnten. + +In der Verzeichnis-Suche kannst du ebenfalls die "booleasche"-Logik zu nutzen. Mit "+lesbisch +Florida" kannst du Leute finden, deren sexuelle Einstellung (oder andere Schlüsselwörter) das Wort "lesbisch" enthält und die in Florida leben. Schau dir den Bereich über "Thematische Tags" auf der "[Tags und Erwähnungen-Seite](help/Tags-and-Mentions) für weitere Informationen, um booleansche Suchen durchzuführen. + +Auf deiner Kontaktseite ist der Link "Ähnliche Interessen", um damit andere Leute zu finden (falls dein Seitenadministrator das globale Verzeichnis nicht ausgeschaltet hat). Hierfür werden die Schlüsselwörter aus deinen öffentlichen und privaten Profilen genutzt, um Personen im globalen Verzeichnis zu finden, die gleiche oder ähnliche Schlüsselwörter haben (deine privaten Schlüsselwörter werden nicht in das globale Verzeichnis übertragen oder gespeichert). Je mehr Schlüsselwörter du einträgst, umso genauer ist die Suche. Das Suchergebnis ist nach Relevanz sortiert. Gegebenenfalls stehst du ganz oben auf der Liste - schließlich bist du die Person, die am besten zu deinen Schlüsselwörtern passt. diff --git a/doc/de/README.md b/doc/de/README.md new file mode 100644 index 0000000000..5dc32ee76e --- /dev/null +++ b/doc/de/README.md @@ -0,0 +1,8 @@ +Friendica-doc-german +==================== + +Friendica - doc - german + +Hier findest du die deutsche Version der Friendica-Hilfedateien. Es handelt sich um eine selbst erstellte, öffentlich freigegebene Arbeit mit dem Ziel, Friendica durch deutsche Hilfedateien für weitere Personen zugänglich zu machen, die dem Englischen nicht ausreichend mächtig sind. + +Die Daten basieren auf dem offiziellen Friendica-Github https://github.com/friendica/friendica (Stand: 03.11.12) diff --git a/doc/de/Remove-Account.md b/doc/de/Remove-Account.md new file mode 100644 index 0000000000..7762ec9d07 --- /dev/null +++ b/doc/de/Remove-Account.md @@ -0,0 +1,24 @@ +Accounts löschen +============== + +* [Zur Startseite der Hilfe](help) + +Wir freuen uns nicht, wenn Leute Friendica verlassen, aber wenn du deinen Account löschen willst, dann besuche die folgende URL + +[Lösche mich (http://NamederSeite/removeme)](../removeme) + +in deinem Webbrowser. Du musst dabei eingeloggt sein. + +Du wirst nach deinem Passwort gefragt, um die Anfrage zu bestätigen. Wenn dieses mit deinem gespeichertem Passwort übereinstimmt, dann wird dein Account sofort gelöscht. Anders als andere Netzwerke, behalten wir die Daten **nicht** für eine gewisse Zeit, falls du deine Meinung noch änderst. Deine Nutzerdetails, deine Unterhaltungen, deine Photos, deine Freunde - alles; wird sofort gelöscht und du wirst ausgeloggt. + +Wenn Beiträge ablaufen, schicken wir Mitteilungen an Friendica, um diese zu löschen. Diaspora hat keine automatische Löschfunktion, so dass diese Funktion in dem Netzwerk deaktiviert ist. Und hoffentlich ist klar, dass das Löschen auch in anderen Netzwerken nicht funktioniert. Wenn du manuell einen Beitrag bzw. eine Reihe von Beiträgen löschst, dann senden wir individuelle Mitteilungen zu Friendica und Diaspora für jeden gelöschten Post. + +Diaspora versäumt dieses oft. + +Wenn du einen Beitrag löscht, aber jemand diesem Beitrag folgt, wird es trotzdem gelöscht. Dein Wunsch hat Priorität. + +Wenn du deinen Account löscht, dann löschen wir alle Beiträge, dein Profil, die Nutzerdaten etc. sofort. + +Um einen Gesamtlöschauftrag zu versenden, bräuchten wir zunächst noch deinen Account; auch, um deinen Freunden zu zeigen, wer diese Anfrage stellt. Das können wir nicht tun, wenn du keinen Account mehr hast. + +Deine Freunde können möglicherweise noch deine Beiträge sehen, wenn dein Account gelöscht wurde, aber es gibt keinen öffentlichen Ort in Friendica mehr, wo diese angeschaut werden können. Wenn du Freunde bei Diaspora hast, kann es sein, dass deine Beiträge weiterhin vorhanden und für andere aus diesem Netzwerk sichtbar sind. diff --git a/doc/de/Settings.md b/doc/de/Settings.md new file mode 100644 index 0000000000..68a6c89f8b --- /dev/null +++ b/doc/de/Settings.md @@ -0,0 +1,226 @@ +Konfigurationen +============== + +* [Zur Startseite der Hilfe](help) + +Hier findest du einige eingebaute Features, welche kein graphisches Interface haben oder nicht dokumentiert sind. Konfigurationseinstellungen sind in der Datei ".htconfig.php" gespeichert. Bearbeite diese Datei, indem du sie z.B. mit einem Texteditor öffnest. Verschiedene Systemeinstellungen sind bereits in dieser Datei dokumentiert und werden hier nicht weiter erklärt. + +**Tastaturbefehle** + +Friendica erfasst die folgenden Tastaturbefehle: + +* [Pause] - Pausiert die Update-Aktivität via "Ajax". Das ist ein Prozess, der Updates durchführt, ohne die Seite neu zu laden. Du kannst diesen Prozess pausieren, um deine Netzwerkauslastung zu reduzieren und/oder um es in der Javascript-Programmierung zum Debuggen zu nutzen. Ein Pausenzeichen erscheint unten links im Fenster. Klicke die [Pause]-Taste ein weiteres Mal, um die Pause zu beenden. + +* [F8] - Zeigt eine Sprachauswahl an + + +**Geburtstagsbenachrichtigung** + +Geburtstage erscheinen auf deiner Startseite für alle Freunde, die in den nächsten 6 Tagen Geburtstag haben. Um deinen Geburtstag für alle sichtbar zu machen, musst du deinen Geburtstag (zumindest Tag und Monat) in dein Standardprofil eintragen. Es ist nicht notwendig, das Jahr einzutragen. + +**Konfigurationseinstellungen** + + +**Sprache** + +Systemeinstellung + +Bitte schau dir die Datei util/README an, um Informationen zur Erstellung einer Übersetzung zu erhalten. + +Konfiguriere: +``` +$a->config['system']['language'] = 'name'; +``` + + +**System-Thema (Design)** + +Systemeinstellung + +Wähle ein Thema als Standardsystemdesign (welches vom Nutzer überschrieben werden kann). Das Standarddesign ist "default". + +Konfiguriere: +``` +$a->config['system']['theme'] = 'theme-name'; +``` + + +**Verifiziere SSL-Zertifikate** + +Sicherheitseinstellungen + +Standardmäßig erlaubt Friendica SSL-Kommunikation von Seiten, die "selbstunterzeichnete" SSL-Zertifikate nutzen. Um eine weitreichende Kompatibilität mit anderen Netzwerken und Browsern zu gewährleisten, empfehlen wir, selbstunterzeichnete Zertifikate **nicht** zu nutzen. Aber wir halten dich nicht davon ab, solche zu nutzen. SSL verschlüsselt alle Daten zwischen den Webseiten (und für deinen Browser), was dir eine komplett verschlüsselte Kommunikation erlaubt. Auch schützt es deine Login-Daten vor Datendiebstahl. Selbstunterzeichnete Zertifikate können kostenlos erstellt werden. Diese Zertifikate können allerdings Opfer eines sogenannten ["man-in-the-middle"-Angriffs](http://de.wikipedia.org/wiki/Man-in-the-middle-Angriff) werden, und sind daher weniger bevorzugt. Wenn du es wünscht, kannst du eine strikte Zertifikatabfrage einstellen. Das führt dazu, dass du keinerlei Verbindung zu einer selbstunterzeichneten SSL-Seite erstellen kannst + +Konfiguriere: +``` +$a->config['system']['verifyssl'] = true; +``` + + +**Erlaubte Freunde-Domains** + +Kooperationen/Gemeinschaften/Bildung Erweiterung + +Kommagetrennte Liste von Domains, welche eine Freundschaft mit dieser Seite eingehen dürfen. Wildcards werden akzeptiert (Wildcard-Unterstützung unter Windows benötigt PHP5.3) Standardmäßig sind alle gültigen Domains erlaubt. + +Konfiguriere: +``` +$a->config['system']['allowed_sites'] = "sitea.com, *siteb.com"; +``` + + +**Erlaubte Email-Domains** + +Kooperationen/Gemeinschaften/Bildung Erweiterung + +Kommagetrennte Liste von Domains, welche bei der Registrierung als Part der Email-Adresse erlaubt sind. Das grenzt Leute aus, die nicht Teil der Gruppe oder Organisation sind. Wildcards werden akzeptiert (Wildcard-Unterstützung unter Windows benötigt PHP5.3) Standardmäßig sind alle gültigen Email-Adressen erlaubt. + +Konfiguriere: +``` +$a->config['system']['allowed_email'] = "sitea.com, *siteb.com"; +``` + +**Öffentlichkeit blockieren** + +Kooperationen/Gemeinschaften/Bildung Erweiterung + +Setze diese Einstellung auf "true" und sperre den öffentlichen Zugriff auf alle Seiten, solange man nicht eingeloggt ist. Das blockiert die Ansicht von Profilen, Freunden, Fotos, vom Verzeichnis und den Suchseiten. Ein Nebeneffekt ist, dass Einträge dieser Seite nicht im globalen Verzeichnis erscheinen. Wir empfehlen, speziell diese Einstellung auszuschalten (die Einstellung ist an anderer Stelle auf dieser Seite erklärt). Beachte: das ist speziell für Seiten, die beabsichtigen, von anderen Friendica-Netzwerken abgeschottet zu sein. Unautorisierte Personen haben ebenfalls nicht die Möglichkeit, Freundschaftsanfragen von Seitennutzern zu beantworten. Die Standardeinstellung steht auf "false". Verfügbar in Version 2.2 und höher. + +Konfiguriere: +``` +$a->config['system']['block_public'] = true; +``` + + +**Veröffentlichung erzwingen** + +Kooperationen/Gemeinschaften/Bildung Erweiterung + +Standardmäßig können Nutzer selbst auswählen, ob ihr Profil im Seitenverzeichnis erscheint. Diese Einstellung zwingt alle Nutzer dazu, im Verzeichnis zu erscheinen. Diese Einstellung kann vom Nutzer nicht deaktiviert werden. Die Standardeinstellung steht auf "false". + +Konfiguriere: +``` +$a->config['system']['publish_all'] = true; +``` + + +**Globales Verzeichnis** + +Kooperationen/Gemeinschaften/Bildung Erweiterung + +Mit diesem Befehl wird die URL eingestellt, die zum Update des globalen Verzeichnisses genutzt wird. Dieser Befehl ist in der Standardkonfiguration enthalten. Der nichtdokumentierte Teil dieser Einstellung ist, dass das globale Verzeichnis gar nicht verfügbar ist, wenn diese Einstellung nicht gesetzt wird. Dies erlaubt eine private Kommunikation, die komplett vom globalen Verzeichnis isoliert ist. + +Konfiguriere: +``` +$a->config['system']['directory_submit_url'] = 'http://dir.friendica.com/submit'; +``` + + +**Proxy Konfigurationseinstellung** + +Wenn deine Seite eine Proxy-Einstellung nutzt, musst du diese Einstellungen vornehmen, um mit anderen Seiten im Internet zu kommunizieren. + +Konfiguriere: +``` +$a->config['system']['proxy'] = "http://proxyserver.domain:port"; +$a->config['system']['proxyuser'] = "username:password"; +``` + + +**Netzwerk-Timeout** + +Legt fest, wie lange das Netzwerk warten soll, bevor ein Timeout eintritt. Der Wert wird in Sekunden angegeben. Standardmäßig ist 60 eingestellt; 0 steht für "unbegrenzt" (nicht empfohlen). + +Konfiguriere: +``` +$a->config['system']['curl_timeout'] = 60; +``` + + +**Banner/Logo** + +Hiermit legst du das Banner der Seite fest. Standardmäßig ist das Friendica-Logo und der Name festgelegt. Du kannst hierfür HTML/CSS nutzen, um den Inhalt zu gestalten und/oder die Position zu ändern, wenn es nicht bereits voreingestellt ist. + +Konfiguriere: +``` +$a->config['system']['banner'] = 'Meine tolle Webseite'; +``` + + +**Maximale Bildgröße** + +Maximale Bild-Dateigröße in Byte. Standardmäßig ist 0 gesetzt, was bedeutet, dass kein Limit gesetzt ist. + +Konfiguriere: +``` +$a->config['system']['maximagesize'] = 1000000; +``` + + +**UTF-8 Reguläre Ausdrücke** + +Während der Registrierung werden die Namen daraufhin geprüft, ob sie reguläre UTF-8-Ausdrücke nutzen. Hierfür wird PHP benötigt, um mit einer speziellen Einstellung kompiliert zu werden, die UTF-8-Ausdrücke benutzt. Wenn du absolut keine Möglichkeit hast, Accounts zu registrieren, setze den Wert von "no_utf" auf "true". Standardmäßig ist "false" eingestellt (das bedeutet, dass UTF-8-Ausdrücke unterstützt werden und funktionieren). + +Konfiguriere: +``` +$a->config['system']['no_utf'] = true; +``` + + +**Prüfe vollständigen Namen** + +Es kann vorkommen, dass viele Spammer versuchen, sich auf deiner Seite zu registrieren. In Testphasen haben wir festgestellt, dass diese automatischen Registrierungen das Feld "Vollständiger Name" oft nur mit Namen ausfüllen, die kein Leerzeichen beinhalten. Wenn du Leuten erlauben willst, sich nur mit einem Namen anzumelden, dann setze die Einstellung auf "true". Die Standardeinstellung ist auf "false" gesetzt. + +Konfiguriere: +``` +$a->config['system']['no_regfullname'] = true; +``` + + +**OpenID** + +Standardmäßig wird OpenID für die Registrierung und für Logins genutzt. Wenn du nicht willst, dass OpenID-Strukturen für dein System übernommen werden, dann setze "no_openid" auf "true". Standardmäßig ist hier "false" gesetzt. + +Konfiguriere: +``` +$a->config['system']['no_openid'] = true; +``` + + +**Multiple Registrierungen** + +Um mehrfache Seiten zu erstellen, muss sich eine Person mehrfach registrieren können. Deine Seiteneinstellung kann Registrierungen komplett blockieren oder an Bedingungen knüpfen. Standardmäßig können eingeloggte Nutzer weitere Accounts für die Seitenerstellung registrieren. Hier ist weiterhin eine Bestätigung notwendig, wenn "REGISTER_APPROVE" ausgewählt ist. Wenn du die Erstellung weiterer Accounts blockieren willst, dann setze die Einstellung "block_extended_register" auf "true". Standardmäßig ist hier "false" gesetzt. + +Konfiguriere: +``` +$a->config['system']['block_extended_register'] = true; +``` + + +**Entwicklereinstellungen** + +Diese sind am nützlichsten, um Protokollprozesse zu debuggen oder andere Kommunikationsfehler einzugrenzen. + +Konfiguriere: +``` +$a->config['system']['debugging'] = true; +$a->config['system']['logfile'] = 'logfile.out'; +$a->config['system']['loglevel'] = LOGGER_DEBUG; +``` +Erstellt detaillierte Debugging-Logfiles, die in der Datei "logfile.out" gespeichert werden (Datei muss auf dem Server mit Schreibrechten versehen sein). "LOGGER_DEBUG" zeigt eine Menge an Systeminformationen, enthält aber keine detaillierten Daten. Du kannst ebenfalls "LOGGER_ALL" auswählen, allerdings empfehlen wir dieses nur, wenn ein spezifisches Problem eingegrenzt werden soll. Andere Log-Level sind möglich, werden aber derzeit noch nicht genutzt. + + +**PHP-Fehler-Logging** + +Nutze die folgenden Einstellungen, um PHP-Fehler direkt in einer Datei zu erfassen. + +Konfiguriere: +``` +error_reporting(E_ERROR | E_WARNING | E_PARSE ); +ini_set('error_log','php.out'); +ini_set('log_errors','1'); +ini_set('display_errors', '0'); +``` + +Diese Befehle erfassen alle PHP-Fehler in der Datei "php.out" (Datei muss auf dem Server mit Schreibrechten versehen sein). Nicht deklarierte Variablen werden manchmal mit einem Verweis versehen, weshalb wir empfehlen, "E_NOTICE" und "E_ALL" nicht zu nutzen. Die Menge an Fehlern, die auf diesem Level gemeldet werden, ist komplett harmlos. Bitte informiere die Entwickler über alle Fehler, die du in deinen Log-Dateien mit den oben genannten Einstellungen erhältst. Sie weisen generell auf Fehler in, die bearbeitet werden müssen. +Wenn du eine leere (weiße) Seite erhältst, schau in die PHP-Log-Datei - dies deutet fast immer darauf hin, dass ein Fehler aufgetreten ist. diff --git a/doc/de/Tags-and-Mentions.md b/doc/de/Tags-and-Mentions.md new file mode 100644 index 0000000000..82cc7ebc55 --- /dev/null +++ b/doc/de/Tags-and-Mentions.md @@ -0,0 +1,31 @@ +Tags und Erwähnungen +================= + +* [Zur Startseite der Hilfe](help) + +Wie viele andere soziale Netzwerke benutzt auch Friendica eine spezielle Schreibweise in seinen Nachrichten, um Tags oder kontextbezogene Links zu anderen Beiträgen hervorzuheben. + +**Erwähnungen** + +Personen werden "getagged", indem du das "@"-Zeichen vor den Namen schreibst. + +Im Folgenden findest du verschiedene Möglichkeiten, um eine Person zu erwähnen: + +* @mike - deutet auf eine Person hin, die im Netzwerk den Namen "mike" nutzt +* @mike_macgirvin - deutet auf eine Person hin, die sich im Netzwerk "Mike Macgirvin" nennt. Beachte, dass Leerzeichen in Tags nicht genutzt werden können. +* @mike+151 - diese Schreibweise deutet auf eine Person hin, die "mike" heißt und deren Kontakt-Identitäts-Nummer 151 ist. Bei der Eingabe erscheint direkt ein Auswahlmenü, sodass du diese Nummer nicht selbst kennen musst. +* @mike@macgirvin.com - diese Schreibweise deutet auf die Profiladresse eines Nutzers in einem anderen Netzwerk oder auf jemanden, der *nicht* in deiner Kontaktliste ist. Diese Schreibweise wird "Fernerwähnung" (remote mention)genannt und kann nur im Email-Stil geschrieben werden, nicht als Internetadresse/URL. + + +Wenn das System ungewollte Erwähnungen nicht blockiert, erhält diese Person eine Mitteilung oder nimmt direkt an der Diskussion teil, wenn es sich um einen öffentlichen Beitrag handelt. Bitte beachte, dass Friendica eingehende "Erwähnungs"-Nachrichten von Personen blockt, die du nicht zu deinem Profil hinzugefügt hast. Diese Maßnahme dient dazu, Spam zu vermeiden. + +"Fernerwähnungen" werden durch das OStatus-Protokoll übermittelt. Dieses Protokoll wird von Friendica, StatusNet und anderen Systemen genutzt, ist allerdings derzeit nicht in Diaspora eingebaut. + +Friendica unterscheidet bei Tags nicht zwischen Personen und Gruppen (einige andere Netzwerke nutzen "!gruppe", um solche zu markieren). + + +**Thematische Tags** + +Thematische Tags werden durch eine "#" gekennzeichnet. Dieses Zeichen erstellen einen Link zur allgemeinen Seitensuche mit dem ausgewählten Begriff. So wird z.B. #Autos zu einer Suche führen, die alle Beiträge deiner Seite umfasst, die dieses Wort erwähnen. Thematische Tags haben generell eine Mindestlänge von 3 Stellen. Kürzere Suchbegriffe finden meist keine Suchergebnisse, wobei dieses abhängig von der Datenbankeinstellung ist. Tags mit einem Leerzeichen werden, wie es auch bei Namen der Fall ist, durch einen Unterstrich gekennzeichnet. Es ist hingegen nicht möglich, Tags zu erstellen, deren gesuchtes Wort einen Unterstrich enthält. + +Thematische Tags werden auch dann nicht verlinkt, wenn sie nur aus Nummern bestehen, wie z.B. #1. Wenn du einen numerischen Tag nutzen willst, füge bitte einen Beschreibungstext hinzu wie z.B. #2012_Wahl. diff --git a/doc/de/Text_comment.md b/doc/de/Text_comment.md new file mode 100644 index 0000000000..5c3d3d4e46 --- /dev/null +++ b/doc/de/Text_comment.md @@ -0,0 +1,47 @@ +Beiträge kommentieren, einordnen und löschen +========================================================== + +* [Zur Startseite der Hilfe](help) + +Hier findest du eine Übersicht über die verschiedenen Möglichkeiten, bestehende Beiträge einzuordnen und zu kommentieren. Achtung: für dieses Beispiel wurde das Thema "Diabook" genutzt. Wenn du ein anderes Design benutzt, wirst du manche dieser Symbole gar nicht oder in anderer Form vorfinden. + + +diabook + +Die einzelnen Symbole + +post_thumbs_up.png Mit diesem Symbol kannst du zeigen, dass dir ein Beitrag gefällt. Falls du diese Eingabe zurücknehmen willst, klicke einfach ein zweites Mal auf das Symbol. +

+ +post_thumbs_down.png Mit diesem Symbol kannst du zeigen, dass dir ein Beitrag nicht gefällt. Falls du diese Eingabe zurücknehmen willst, klicke einfach ein zweites Mal auf das Symbol. +

+ +post_share.png Mit diesem Symbol kannst du einen Beitrag weiter verteilen. Einfach anklicken und sofort erscheint der Beitrag in deinem Beitragseditor. Am Ende des eingefügten Beitrags erscheint ein Link zum Originalbeitrag. +

+ +post_mark.png Mit diesem Symbol kannst du einen Beitrag für dich markieren. Markierte Beiträge erscheinen in deiner Netzwerk-Seite unter "Markierte". Wenn du die Markierung entfernen willst, klicke einfach ein zweites Mal auf das Symbol. +

+ +post_tag.png Mit diesem Symbol kannst du einen tag zum Beitrag hinzufügen und diesen so einem bestimmten Schlagwort zuzuordnen. Anschließend kannst du auf diesen tag klicken und alle Beiträge mit diesem tag ansehen. ACHTUNG: tags können nicht mehr entfernt werden. +

+ +post_categorize.png Mit diesem Symbol ist es möglich, die Beiträge in bestimmte Gruppen einzuordnen. Dies dient dazu, gewählte Beiträge nach eigenen Vorstellungen zu sortieren und wieder zu finden. Wähle eine vorhandene Gruppe oder gib einen neuen Namen ein. Die erstellten Gruppen findest du unter "Gespeicherte Ordner" in der Netzwerk-Ansicht. +

+ +post_delete.png Mit diesem Symbol löschst du deinen eigenen Beitrag bzw. entfernst einen Beitrag einer anderen Person aus deinem Stream. +

+ +post_choose.png Mit diesem Symbol kannst du mehrere Beiträge auswählen und gesammelt löschen. Hierfür gehst du nach dem Markieren aller gewünschten Beiträge auf "Lösche die markierten Beiträge" am Ende der Seite mit allen Beiträgen. +

+ +**Im Folgenden findest du Symbole weiterer Themen** + +Darkbubble darkbubble.png + +Darkzero darkzero.png + +(inkl. weiterer "zero"-Themen, slackr, comix, easterbunny, facepark) + +Dispy dispy.png (inkl. smoothly, testbubble) + +Frost Mobile frost.png diff --git a/doc/de/Text_editor.md b/doc/de/Text_editor.md new file mode 100644 index 0000000000..191ada8129 --- /dev/null +++ b/doc/de/Text_editor.md @@ -0,0 +1,41 @@ +Beiträge erstellen +================= + +* [Zur Startseite der Hilfe](help) + +Hier findest du eine Übersicht über die verschiedenen Möglichkeiten, deinen Beitrag zu bearbeiten. Achtung: für dieses Beispiel wurde das Thema "Diabook" genutzt. Wenn du ein anderes Design benutzt, wird du manche dieser Symbole gar nicht oder in anderer Form vorfinden. + + +editor + +Die einzelnen Symbole + +editor Wenn du auf dieses Symbol klickst, dann kannst du ein Bild von deinem Computer hinzufügen. Wenn du eine Internetadresse (URL) eingeben willst, dann kannst du das "Baum"-Symbol im oberen Teil des Editors nutzen. Wenn du ein Bild ausgewählt hast, dann erscheint eine Miniaturdarstellung des Bildes im Editor. +

+ +paper_clip Wenn du dieses Symbol anklickst, dann kannst du weitere Dateien von deinem Computer einfügen. Eine Vorschau des Dateiinhalts erfolgt nicht. +

+ +chain Wenn du die Kette anklickst, dann kannst du eine Internetadresse (URL) einfügen. Im Editor erscheint automatisch eine kurze Information zum eingefügten Link. +

+ +video Wenn du dieses Symbol wählst, dann kannst du eine Internetadresse (URL) zu einem Video einfügen. Eine Miniaturansicht des Videos erscheint in der Vorschau bzw. im fertigen Beitrag. +

+ +mic Wenn du dieses Symbol wählst, dann kannst du eine Internetadresse (URL) zu einer Sound-Datei einfügen. Ein Player erscheint in der Vorschau bzw. im fertigen Beitrag. +

+ +globe Wenn du dieses Symbol wählst, dann kannst du deinen Standort festlegen. Hier reicht schon eine Angabe wie "Berlin" oder "10775". Dieser Eintrag führt anschließend zu einer Suchanfrage bei Google Maps. +

+ +**Im Folgenden findest du Symbole weiterer Themen** + +Cleanzero cleanzero.png + +(inkl. weiterer "zero"-Themen, comix, easterbunny, facepark, slackr + +Darkbubble darkbubble.png (inkl. smoothly, testbubble) + +Frost frost.png + +Vier vier.png (inkl. dispy) diff --git a/doc/de/andfinally.md b/doc/de/andfinally.md new file mode 100644 index 0000000000..06a1878ba9 --- /dev/null +++ b/doc/de/andfinally.md @@ -0,0 +1,27 @@ +... und zuletzt +=============== + +Und damit sind wir auch schon am Ende der Schnellstartanleitung. + +Hier sind noch einige weitere Dinge, die dir den Start vereinfachen können. + +**Gruppen** + + +- Neu hier? - eine Gruppe für Leute, die neu bei Friendica sind + +- Friendica Support - Probleme? Dann ist das der Platz, um zu fragen! + +- Öffentlicher Stream - ein Platz, um über alles mit jedem zu reden. + +- Let's Talk eine Gruppe, um Leute und Gruppen mit gleichen Interessen zu finden + +- Local Friendica eine Seite für lokale Friendica-Gruppen + + +**Dokumentation** + +- Zu weiteren Netzwerken verbinden +- Zur Startseite der Hilfe + + diff --git a/doc/de/groupsandpages.md b/doc/de/groupsandpages.md new file mode 100644 index 0000000000..0b5f1fcedd --- /dev/null +++ b/doc/de/groupsandpages.md @@ -0,0 +1,16 @@ +Gruppen und Seiten +========== + +* [Zur Startseite der Hilfe](help) + +Hier siehst du das globale Verzeichnis. Wenn du dich mal verirrt hast, kannst du diesen Link klicken und wieder hierher kommen. + +Auf dieser Seite findest du eine Zusammenstellung von Gruppen, Foren und bekannten Seiten. Gruppen sind keine realen Personen. Sich mit diesen zu verbinden ist, als wenn man jemanden auf Facebook "liked" ("gefällt mir") oder wenn man sich in einem Forum anmeldet. Habe keine Sorge, falls du dich unbehaglich fühlst, wenn du dich einer neuen Person vorstellen sollst, da es sich nicht um Personen handelt. + +Wenn du dich mit einer Gruppe verbindest, erscheinen alle Nachrichten der Gruppe in deinem "Netzwerk"-Tab. Du kannst diese Beiträge kommentieren oder selbst in der Gruppe schreiben, ohne eine der Gruppenmitglieder persönlich hinzuzufügen. Das ist ein großartiger Weg, dynamisch neue Freunde zu gewinnen. Du findest Personen, die du magst, anstatt Fremde hinzuzufügen. Suche dir einfach eine Gruppe und füge sie so hinzu, wie du auch normale Freunde hinzufügst. Es gibt eine Menge Gruppen und möglicherweise findest du nicht wieder zu dieser Seite zurück. In diesem Fall nutze einfach den Link oben auf dieser Seite. + +Wenn du einige Gruppen hinzugefügt hast, gehe weiter zum nächsten Schritt. + + + + diff --git a/doc/de/guide.md b/doc/de/guide.md new file mode 100644 index 0000000000..a5ce54a1dc --- /dev/null +++ b/doc/de/guide.md @@ -0,0 +1,17 @@ +Erste Schritte... +========== + +* [Zur Startseite der Hilfe](help) + +Das Erste zum Anfang: geh sicher, dass du schon eingeloggt bist. Wenn du noch nicht eingeloggt bist, kannst du das in dem Fenster unten machen. + +Sobald du eingeloggt bist (oder wenn du bereits eingeloggt bist), kannst du unten nun auf deine Profilseite schauen. + +Hier sieht es ein wenig wie auf deiner Facebook-Seite aus. Hier findest du alle deine Statusmeldungen und Nachrichten deiner Freunde, die direkt auf deine Seite ("Wall") geschrieben haben. Um deinen Status einzutragen, klicke einfach auf die Box oben, in der "Teilen" steht. Wenn du das machst, vergrößert sich die Box. Nun kannst du einige Formatierungsoptionen wie Fett, kursiv, unterstrichen auswählen und ebenfalls Bilder und Links hinzufügen. Unten findest du in diesem Feld weitere Links, mit denen du Bilder und Dateien von deinem Computer hochladen, Webseiten mit einem Kurztext teilen und Video- und Audiodateien aus dem Internet einfügen kannst. Außerdem kannst du hier eintragen, wo du gerade bist. + +Wenn du deinen Beitrag ("Post") geschrieben hast, kannst du auf das "Schloss"-Symbol klicken und festlegen, wer deinen Beitrag sehen kann. Wenn du dieses Symbol nicht anklickst, ist dein Beitrag öffentlich. Das bedeutet, dass jeder, der dein Profil ansieht, der auf dem "Community"-Tab deines Servers oder auf dem "Netzwerk"-Tab ("Beiträge deiner Kontakte") eines befreundeten Kontakts ist, den Beitrag sehen kann. + +Probiere es doch einfach mal aus. Wenn du fertg bist, schauen wir uns den "Netzwerk"-Tab an. + + + diff --git a/doc/de/makingnewfriends.md b/doc/de/makingnewfriends.md new file mode 100644 index 0000000000..911b7f4a89 --- /dev/null +++ b/doc/de/makingnewfriends.md @@ -0,0 +1,16 @@ +Neue Freunde finden +============== + +* [Zur Startseite der Hilfe](help) + +Hier siehst du die Kontaktvorschläge. Wenn du dich mal verirrt hast, kannst du diesen Link klicken und wieder hierher kommen. + +Diese Seite ist ein wenig wie die Kontaktvorschläge in Facebook. Jeder auf dieser Liste hat zugestimmt, als Kontaktvorschlag zu erscheinen. Das bedeutet, das sie Anfragen meist nicht ablehnen, da sie neue Leute kennenlernen wollen. + +Siehst du jemanden, dessen Aussehen du magst? Klicke auf den "Verbinden"-Button beim Foto. Als nächstes kommst du zur Seite "Freundschafts-/Kontaktanfrage". Fülle das Formular wie vorgegeben aus und trage optional eine kleine Notiz ein. Nun musst du nur noch auf die Bestätigung warten. Beachte dabei, dass es sich um reale Personen handelt und es somit etwas dauern kann. Jetzt, nachdem du jemanden hinzugefügt hast, weißt du vielleicht nicht mehr, wie du zurückkommst. Klicke einfach auf den Link oben auf dieser Seite und du kommst zurück zur Seite mit den Kontaktvorschlägen, um weitere Personen hinzuzufügen. + +Du willst nicht einfach Personen hinzufügen, die du nicht kennst? Kein Problem - an dieser Stelle kommen wir zu den Gruppen und Seiten. + + + + diff --git a/doc/de/network.md b/doc/de/network.md new file mode 100644 index 0000000000..37eeec869d --- /dev/null +++ b/doc/de/network.md @@ -0,0 +1,14 @@ +Deine "Netzwerk"-Seite +============== + +* [Zur Startseite der Hilfe](help) + +Das ist dein "Netzwerk"-Tab. Wenn du dich mal verirrt hast, kannst du diesen Link klicken, um wieder hierher zu kommen. + +Diese Seite ist ein wenig wie die News-Seite in Facebook oder der Stream in Diaspora. Hier findest du alle Beiträge deiner Kontakte, Gruppen und Feeds, die du eingetragen hast. Wenn du neu bist, siehst du hier noch nichts, falls du deinen Status im letzten Schritt noch nicht eingetragen hast. Wenn du bereits ein paar Freunde eingetragen hast, findest du hier ihre Beiträge. Hier kannst du Beiträge kommentieren, eintragen, dass du den Beitrag magst oder ablehnst oder die Profile durch einen Klick auf deren Namen anschauen und auf deren Seite ("Wall") Nachrichten schreiben. + +Nun wollen wir diese Seite mit Inhalt füllen. Der erste Schritt ist es, Leute zu deinem Account hinzuzufügen. + + + + diff --git a/doc/img/camera.png b/doc/img/camera.png new file mode 100644 index 0000000000..af73b0d73f Binary files /dev/null and b/doc/img/camera.png differ diff --git a/doc/img/chain.png b/doc/img/chain.png new file mode 100644 index 0000000000..c8f8bf1f34 Binary files /dev/null and b/doc/img/chain.png differ diff --git a/doc/img/darkbubble.png b/doc/img/darkbubble.png new file mode 100644 index 0000000000..1ca0ba5fb7 Binary files /dev/null and b/doc/img/darkbubble.png differ diff --git a/doc/img/darkzero.png b/doc/img/darkzero.png new file mode 100644 index 0000000000..7c6aa177a5 Binary files /dev/null and b/doc/img/darkzero.png differ diff --git a/doc/img/diabook.png b/doc/img/diabook.png new file mode 100644 index 0000000000..5c55baaafc Binary files /dev/null and b/doc/img/diabook.png differ diff --git a/doc/img/dispy.png b/doc/img/dispy.png new file mode 100644 index 0000000000..57387a781c Binary files /dev/null and b/doc/img/dispy.png differ diff --git a/doc/img/editor_darkbubble.png b/doc/img/editor_darkbubble.png new file mode 100644 index 0000000000..6bad6ef50f Binary files /dev/null and b/doc/img/editor_darkbubble.png differ diff --git a/doc/img/editor_frost.png b/doc/img/editor_frost.png new file mode 100644 index 0000000000..39bf11e3e6 Binary files /dev/null and b/doc/img/editor_frost.png differ diff --git a/doc/img/editor_vier.png b/doc/img/editor_vier.png new file mode 100644 index 0000000000..67df8aed84 Binary files /dev/null and b/doc/img/editor_vier.png differ diff --git a/doc/img/editor_zero.png b/doc/img/editor_zero.png new file mode 100644 index 0000000000..c1607396c3 Binary files /dev/null and b/doc/img/editor_zero.png differ diff --git a/doc/img/friendica_editor.png b/doc/img/friendica_editor.png new file mode 100644 index 0000000000..397c4b5958 Binary files /dev/null and b/doc/img/friendica_editor.png differ diff --git a/doc/img/frost.png b/doc/img/frost.png new file mode 100644 index 0000000000..011598b130 Binary files /dev/null and b/doc/img/frost.png differ diff --git a/doc/img/globe.png b/doc/img/globe.png new file mode 100644 index 0000000000..b8a319db09 Binary files /dev/null and b/doc/img/globe.png differ diff --git a/doc/img/mic.png b/doc/img/mic.png new file mode 100644 index 0000000000..616e798592 Binary files /dev/null and b/doc/img/mic.png differ diff --git a/doc/img/padlock.png b/doc/img/padlock.png new file mode 100644 index 0000000000..40f60cfecf Binary files /dev/null and b/doc/img/padlock.png differ diff --git a/doc/img/paper_clip.png b/doc/img/paper_clip.png new file mode 100644 index 0000000000..1d9cc0a092 Binary files /dev/null and b/doc/img/paper_clip.png differ diff --git a/doc/img/post_categorize.png b/doc/img/post_categorize.png new file mode 100644 index 0000000000..54a3118701 Binary files /dev/null and b/doc/img/post_categorize.png differ diff --git a/doc/img/post_choose.png b/doc/img/post_choose.png new file mode 100644 index 0000000000..762cea8682 Binary files /dev/null and b/doc/img/post_choose.png differ diff --git a/doc/img/post_delete.png b/doc/img/post_delete.png new file mode 100644 index 0000000000..3d0a92e83c Binary files /dev/null and b/doc/img/post_delete.png differ diff --git a/doc/img/post_link.png b/doc/img/post_link.png new file mode 100644 index 0000000000..b28fca3b9f Binary files /dev/null and b/doc/img/post_link.png differ diff --git a/doc/img/post_mark.png b/doc/img/post_mark.png new file mode 100644 index 0000000000..6bee82f18f Binary files /dev/null and b/doc/img/post_mark.png differ diff --git a/doc/img/post_share.png b/doc/img/post_share.png new file mode 100644 index 0000000000..f5821692e5 Binary files /dev/null and b/doc/img/post_share.png differ diff --git a/doc/img/post_tag.png b/doc/img/post_tag.png new file mode 100644 index 0000000000..dc9826c23a Binary files /dev/null and b/doc/img/post_tag.png differ diff --git a/doc/img/post_thumbs_down.png b/doc/img/post_thumbs_down.png new file mode 100644 index 0000000000..818dcf504e Binary files /dev/null and b/doc/img/post_thumbs_down.png differ diff --git a/doc/img/post_thumbs_up.png b/doc/img/post_thumbs_up.png new file mode 100644 index 0000000000..a3562c3869 Binary files /dev/null and b/doc/img/post_thumbs_up.png differ diff --git a/doc/img/posts_define.png b/doc/img/posts_define.png new file mode 100644 index 0000000000..376a161c51 Binary files /dev/null and b/doc/img/posts_define.png differ diff --git a/doc/img/video.png b/doc/img/video.png new file mode 100644 index 0000000000..be023d746d Binary files /dev/null and b/doc/img/video.png differ diff --git a/doc/network.md b/doc/network.md index afb0923950..08ddca290e 100644 --- a/doc/network.md +++ b/doc/network.md @@ -2,7 +2,7 @@ This is your Network Tab. If you get lost, you can click This is a bit like the Newsfeed at Facebook or the Stream at Diaspora. It's where all the posts from your contacts, groups, and feeds will appear. If you're new, you won't see anything in this page, unless you posted your status in the last step. If you've already added a few friends, you'll be able to see their posts. Here, you can comment, like, or dislike posts, or click on somebody's name to visit their profile page where you can write on their wall. -Now we need to fill it up, the first step, is to add people you already know from Facebook. +Now we need to fill it up, the first step, is to make some new friends. diff --git a/doc/peopleyouknow.md b/doc/peopleyouknow.md deleted file mode 100644 index ae0c9ef598..0000000000 --- a/doc/peopleyouknow.md +++ /dev/null @@ -1,13 +0,0 @@ -This is your connector settings page. If you get lost, you can click this link to bring yourself back here. - -This is the bit that makes Friendica unique. You can connect to anybody on the internet from your Friendica account using this page! The available connectors varies depending on which plugins you have installed, but for now, we'll walk you through Facebook. Note that not all servers have the Facebook connector installed. If you can't find it in the list below, don't worry, we'll look at ways of connecting to more people in the following pages. - -The biggest of all social networks is Facebook. Fortunately, this connector is really easy. Scroll down the page, and click Facebook Connector Settings. Enter your Facebook user name and password and let the application (the connector) do everything the options suggest. You can fine tune this or experiment with the other connectors too. If you need help, you can always ask at Friendica Support or see the instructions here. - -When you're ready, we can move on to making new friends. - - - - - - diff --git a/htconfig.php b/htconfig.php index de7674c9ae..ab9a7ca506 100644 --- a/htconfig.php +++ b/htconfig.php @@ -54,8 +54,8 @@ $a->config['php_path'] = 'php'; // You shouldn't need to change anything else. // Location of global directory submission page. -$a->config['system']['directory_submit_url'] = 'http://dir.friendika.com/submit'; -$a->config['system']['directory_search_url'] = 'http://dir.friendika.com/directory?search='; +$a->config['system']['directory_submit_url'] = 'http://dir.friendica.com/submit'; +$a->config['system']['directory_search_url'] = 'http://dir.friendica.com/directory?search='; // PuSH - aka pubsubhubbub URL. This makes delivery of public posts as fast as private posts diff --git a/images/friendika-128.jpg b/images/friendika-128.jpg deleted file mode 100644 index f7d86ae50d..0000000000 Binary files a/images/friendika-128.jpg and /dev/null differ diff --git a/images/friendika-128.png b/images/friendika-128.png deleted file mode 100644 index d2792ab54e..0000000000 Binary files a/images/friendika-128.png and /dev/null differ diff --git a/images/friendika-16.jpg b/images/friendika-16.jpg deleted file mode 100644 index ce59a70a01..0000000000 Binary files a/images/friendika-16.jpg and /dev/null differ diff --git a/images/friendika-16.png b/images/friendika-16.png deleted file mode 100644 index 745b7ac6c5..0000000000 Binary files a/images/friendika-16.png and /dev/null differ diff --git a/images/friendika-1600.png b/images/friendika-1600.png deleted file mode 100644 index 615a81dd94..0000000000 Binary files a/images/friendika-1600.png and /dev/null differ diff --git a/images/friendika-256.jpg b/images/friendika-256.jpg deleted file mode 100644 index 182810d625..0000000000 Binary files a/images/friendika-256.jpg and /dev/null differ diff --git a/images/friendika-256.png b/images/friendika-256.png deleted file mode 100644 index e965fbabab..0000000000 Binary files a/images/friendika-256.png and /dev/null differ diff --git a/images/friendika-32.jpg b/images/friendika-32.jpg deleted file mode 100644 index 4e697411d6..0000000000 Binary files a/images/friendika-32.jpg and /dev/null differ diff --git a/images/friendika-32.png b/images/friendika-32.png deleted file mode 100644 index 61764bf20a..0000000000 Binary files a/images/friendika-32.png and /dev/null differ diff --git a/images/friendika-48.png b/images/friendika-48.png deleted file mode 100644 index f858d9a81a..0000000000 Binary files a/images/friendika-48.png and /dev/null differ diff --git a/images/friendika-64.jpg b/images/friendika-64.jpg deleted file mode 100644 index 050830fff9..0000000000 Binary files a/images/friendika-64.jpg and /dev/null differ diff --git a/images/friendika-64.png b/images/friendika-64.png deleted file mode 100644 index 88cb8b016d..0000000000 Binary files a/images/friendika-64.png and /dev/null differ diff --git a/images/friendika-96.png b/images/friendika-96.png deleted file mode 100644 index 76a102ef2e..0000000000 Binary files a/images/friendika-96.png and /dev/null differ diff --git a/images/friendika.svg b/images/friendika.svg deleted file mode 100644 index 2155d0b006..0000000000 --- a/images/friendika.svg +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - diff --git a/images/smiley-beard.png b/images/smiley-beard.png deleted file mode 100644 index 5d4b28463f..0000000000 Binary files a/images/smiley-beard.png and /dev/null differ diff --git a/images/smiley-whitebeard.png b/images/smiley-whitebeard.png deleted file mode 100644 index 2a1fccbb72..0000000000 Binary files a/images/smiley-whitebeard.png and /dev/null differ diff --git a/images/tag.png b/images/tag.png deleted file mode 100644 index 40c5fd44e2..0000000000 Binary files a/images/tag.png and /dev/null differ diff --git a/images/tag_b.png b/images/tag_b.png deleted file mode 100644 index 66c03415de..0000000000 Binary files a/images/tag_b.png and /dev/null differ diff --git a/include/Contact.php b/include/Contact.php index 14e1a52cdd..ecc271a8e0 100644 --- a/include/Contact.php +++ b/include/Contact.php @@ -192,6 +192,7 @@ function contact_photo_menu($contact) { $status_link=""; $photos_link=""; $posts_link=""; + $poke_link=""; $sparkle = false; if($contact['network'] === NETWORK_DFRN) { @@ -211,10 +212,12 @@ function contact_photo_menu($contact) { $pm_url = $a->get_baseurl() . '/message/new/' . $contact['id']; } + $poke_link = $a->get_baseurl() . '/poke/?f=&c=' . $contact['id']; $contact_url = $a->get_baseurl() . '/contacts/' . $contact['id']; $posts_link = $a->get_baseurl() . '/network/?cid=' . $contact['id']; $menu = Array( + t("Poke") => $poke_link, t("View Status") => $status_link, t("View Profile") => $profile_link, t("View Photos") => $photos_link, diff --git a/include/Photo.php b/include/Photo.php index ba4241a7b8..8fd581977d 100644 --- a/include/Photo.php +++ b/include/Photo.php @@ -3,31 +3,141 @@ if(! class_exists("Photo")) { class Photo { - private $image; - private $width; - private $height; - private $valid; - private $type; - private $types; + private $image; - /** - * supported mimetypes and corresponding file extensions - */ - static function supportedTypes() { - $t = array(); - $t['image/jpeg'] ='jpg'; - if (imagetypes() & IMG_PNG) $t['image/png'] = 'png'; - return $t; - } + /** + * Put back gd stuff, not everybody have Imagick + */ + private $imagick; + private $width; + private $height; + private $valid; + private $type; + private $types; - public function __construct($data, $type="image/jpeg") { + /** + * supported mimetypes and corresponding file extensions + */ + static function supportedTypes() { + if(class_exists('Imagick')) { + /** + * Imagick::queryFormats won't help us a lot there... + * At least, not yet, other parts of friendica uses this array + */ + $t = array( + 'image/jpeg' => 'jpg', + 'image/png' => 'png', + 'image/gif' => 'gif' + ); + } else { + $t = array(); + $t['image/jpeg'] ='jpg'; + if (imagetypes() & IMG_PNG) $t['image/png'] = 'png'; + } - $this->types = $this->supportedTypes(); - if (!array_key_exists($type,$this->types)){ - $type='image/jpeg'; + return $t; + } + + public function __construct($data, $type=null) { + $this->imagick = class_exists('Imagick'); + $this->types = $this->supportedTypes(); + if (!array_key_exists($type,$this->types)){ + $type='image/jpeg'; + } + $this->type = $type; + + if($this->is_imagick() && $this->load_data($data)) { + return true; + } else { + // Failed to load with Imagick, fallback + $this->imagick = false; } + return $this->load_data($data); + } + + public function __destruct() { + if($this->image) { + if($this->is_imagick()) { + $this->image->clear(); + $this->image->destroy(); + return; + } + imagedestroy($this->image); + } + } + + public function is_imagick() { + return $this->imagick; + } + + /** + * Maps Mime types to Imagick formats + */ + public function get_FormatsMap() { + $m = array( + 'image/jpeg' => 'JPG', + 'image/png' => 'PNG', + 'image/gif' => 'GIF' + ); + return $m; + } + + private function load_data($data) { + if($this->is_imagick()) { + $this->image = new Imagick(); + try { + $this->image->readImageBlob($data); + } + catch (Exception $e) { + // Imagick couldn't use the data + return false; + } + + /** + * Setup the image to the format it will be saved to + */ + $map = $this->get_FormatsMap(); + $format = $map[$type]; + $this->image->setFormat($format); + + // Always coalesce, if it is not a multi-frame image it won't hurt anyway + $this->image = $this->image->coalesceImages(); + + /** + * setup the compression here, so we'll do it only once + */ + switch($this->getType()){ + case "image/png": + $quality = get_config('system','png_quality'); + if((! $quality) || ($quality > 9)) + $quality = PNG_QUALITY; + /** + * From http://www.imagemagick.org/script/command-line-options.php#quality: + * + * 'For the MNG and PNG image formats, the quality value sets + * the zlib compression level (quality / 10) and filter-type (quality % 10). + * The default PNG "quality" is 75, which means compression level 7 with adaptive PNG filtering, + * unless the image has a color map, in which case it means compression level 7 with no PNG filtering' + */ + $quality = $quality * 10; + $this->image->setCompressionQuality($quality); + break; + case "image/jpeg": + $quality = get_config('system','jpeg_quality'); + if((! $quality) || ($quality > 100)) + $quality = JPEG_QUALITY; + $this->image->setCompressionQuality($quality); + } + + // The 'width' and 'height' properties are only used by non-Imagick routines. + $this->width = $this->image->getImageWidth(); + $this->height = $this->image->getImageHeight(); + $this->valid = true; + + return true; + } + $this->valid = false; - $this->type = $type; $this->image = @imagecreatefromstring($data); if($this->image !== FALSE) { $this->width = imagesx($this->image); @@ -35,463 +145,606 @@ class Photo { $this->valid = true; imagealphablending($this->image, false); imagesavealpha($this->image, true); - } - } - public function __destruct() { - if($this->image) - imagedestroy($this->image); - } - - public function is_valid() { - return $this->valid; - } - - public function getWidth() { - return $this->width; - } - - public function getHeight() { - return $this->height; - } - - public function getImage() { - return $this->image; - } - - public function getType() { - return $this->type; - } - public function getExt() { - return $this->types[$this->type]; - } - - public function scaleImage($max) { - - $width = $this->width; - $height = $this->height; - - $dest_width = $dest_height = 0; - - if((! $width)|| (! $height)) - return FALSE; - - if($width > $max && $height > $max) { - if($width > $height) { - $dest_width = $max; - $dest_height = intval(( $height * $max ) / $width); - } - else { - $dest_width = intval(( $width * $max ) / $height); - $dest_height = $max; - } - } - else { - if( $width > $max ) { - $dest_width = $max; - $dest_height = intval(( $height * $max ) / $width); - } - else { - if( $height > $max ) { - $dest_width = intval(( $width * $max ) / $height); - $dest_height = $max; - } - else { - $dest_width = $width; - $dest_height = $height; - } - } - } - - - $dest = imagecreatetruecolor( $dest_width, $dest_height ); - imagealphablending($dest, false); - imagesavealpha($dest, true); - if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha - imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height); - if($this->image) - imagedestroy($this->image); - $this->image = $dest; - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - - } - - public function rotate($degrees) { - $this->image = imagerotate($this->image,$degrees,0); - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - } - - public function flip($horiz = true, $vert = false) { - $w = imagesx($this->image); - $h = imagesy($this->image); - $flipped = imagecreate($w, $h); - if($horiz) { - for ($x = 0; $x < $w; $x++) { - imagecopy($flipped, $this->image, $x, 0, $w - $x - 1, 0, 1, $h); - } - } - if($vert) { - for ($y = 0; $y < $h; $y++) { - imagecopy($flipped, $this->image, 0, $y, 0, $h - $y - 1, $w, 1); - } - } - $this->image = $flipped; - } - - public function orient($filename) { - // based off comment on http://php.net/manual/en/function.imagerotate.php - - if( (! function_exists('exif_read_data')) || ($this->getType() !== 'image/jpeg') ) - return; - - $exif = exif_read_data($filename); - $ort = $exif['Orientation']; - - switch($ort) - { - case 1: // nothing - break; - - case 2: // horizontal flip - $this->flip(); - break; - - case 3: // 180 rotate left - $this->rotate(180); - break; - - case 4: // vertical flip - $this->flip(false, true); - break; - - case 5: // vertical flip + 90 rotate right - $this->flip(false, true); - $this->rotate(-90); - break; - - case 6: // 90 rotate right - $this->rotate(-90); - break; - - case 7: // horizontal flip + 90 rotate right - $this->flip(); - $this->rotate(-90); - break; - - case 8: // 90 rotate left - $this->rotate(90); - break; - } - } - - - - public function scaleImageUp($min) { - - $width = $this->width; - $height = $this->height; - - $dest_width = $dest_height = 0; - - if((! $width)|| (! $height)) - return FALSE; - - if($width < $min && $height < $min) { - if($width > $height) { - $dest_width = $min; - $dest_height = intval(( $height * $min ) / $width); - } - else { - $dest_width = intval(( $width * $min ) / $height); - $dest_height = $min; - } - } - else { - if( $width < $min ) { - $dest_width = $min; - $dest_height = intval(( $height * $min ) / $width); - } - else { - if( $height < $min ) { - $dest_width = intval(( $width * $min ) / $height); - $dest_height = $min; - } - else { - $dest_width = $width; - $dest_height = $height; - } - } - } - - - $dest = imagecreatetruecolor( $dest_width, $dest_height ); - imagealphablending($dest, false); - imagesavealpha($dest, true); - if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha - imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height); - if($this->image) - imagedestroy($this->image); - $this->image = $dest; - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - - } - - - - public function scaleImageSquare($dim) { - - $dest = imagecreatetruecolor( $dim, $dim ); - imagealphablending($dest, false); - imagesavealpha($dest, true); - if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha - imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dim, $dim, $this->width, $this->height); - if($this->image) - imagedestroy($this->image); - $this->image = $dest; - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - } - - - public function cropImage($max,$x,$y,$w,$h) { - $dest = imagecreatetruecolor( $max, $max ); - imagealphablending($dest, false); - imagesavealpha($dest, true); - if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha - imagecopyresampled($dest, $this->image, 0, 0, $x, $y, $max, $max, $w, $h); - if($this->image) - imagedestroy($this->image); - $this->image = $dest; - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - } - - public function saveImage($path) { - switch($this->type){ - case "image/png": - $quality = get_config('system','png_quality'); - if((! $quality) || ($quality > 9)) - $quality = PNG_QUALITY; - imagepng($this->image, $path, $quality); - break; - default: - $quality = get_config('system','jpeg_quality'); - if((! $quality) || ($quality > 100)) - $quality = JPEG_QUALITY; - imagejpeg($this->image,$path,$quality); + return true; } + return false; } - public function imageString() { - ob_start(); - switch($this->type){ - case "image/png": - $quality = get_config('system','png_quality'); - if((! $quality) || ($quality > 9)) - $quality = PNG_QUALITY; - imagepng($this->image,NULL, $quality); - break; - default: - $quality = get_config('system','jpeg_quality'); - if((! $quality) || ($quality > 100)) - $quality = JPEG_QUALITY; + public function is_valid() { + if($this->is_imagick()) + return ($this->image !== FALSE); + return $this->valid; + } - imagejpeg($this->image,NULL,$quality); + public function getWidth() { + if(!$this->is_valid()) + return FALSE; + + if($this->is_imagick()) + return $this->image->getImageWidth(); + return $this->width; + } + + public function getHeight() { + if(!$this->is_valid()) + return FALSE; + + if($this->is_imagick()) + return $this->image->getImageHeight(); + return $this->height; + } + + public function getImage() { + if(!$this->is_valid()) + return FALSE; + + if($this->is_imagick()) { + /* Clean it */ + $this->image = $this->image->deconstructImages(); + return $this->image; + } + return $this->image; + } + + public function getType() { + if(!$this->is_valid()) + return FALSE; + + return $this->type; + } + + public function getExt() { + if(!$this->is_valid()) + return FALSE; + + return $this->types[$this->getType()]; + } + + public function scaleImage($max) { + if(!$this->is_valid()) + return FALSE; + + $width = $this->getWidth(); + $height = $this->getHeight(); + + $dest_width = $dest_height = 0; + + if((! $width)|| (! $height)) + return FALSE; + + if($width > $max && $height > $max) { + + // very tall image (greater than 16:9) + // constrain the width - let the height float. + + if((($height * 9) / 16) > $width) { + $dest_width = $max; + $dest_height = intval(( $height * $max ) / $width); + } + + // else constrain both dimensions + + elseif($width > $height) { + $dest_width = $max; + $dest_height = intval(( $height * $max ) / $width); + } + else { + $dest_width = intval(( $width * $max ) / $height); + $dest_height = $max; + } + } + else { + if( $width > $max ) { + $dest_width = $max; + $dest_height = intval(( $height * $max ) / $width); + } + else { + if( $height > $max ) { + + // very tall image (greater than 16:9) + // but width is OK - don't do anything + + if((($height * 9) / 16) > $width) { + $dest_width = $width; + $dest_height = $height; + } + else { + $dest_width = intval(( $width * $max ) / $height); + $dest_height = $max; + } + } + else { + $dest_width = $width; + $dest_height = $height; + } + } + } + + + if($this->is_imagick()) { + /** + * If it is not animated, there will be only one iteration here, + * so don't bother checking + */ + // Don't forget to go back to the first frame + $this->image->setFirstIterator(); + do { + + // FIXME - implement horizantal bias for scaling as in followin GD functions + // to allow very tall images to be constrained only horizontally. + + $this->image->scaleImage($dest_width, $dest_height); + } while ($this->image->nextImage()); + + // These may not be necessary any more + $this->width = $this->image->getImageWidth(); + $this->height = $this->image->getImageHeight(); + + return; + } + + + $dest = imagecreatetruecolor( $dest_width, $dest_height ); + imagealphablending($dest, false); + imagesavealpha($dest, true); + if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha + imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height); + if($this->image) + imagedestroy($this->image); + $this->image = $dest; + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); + } + + public function rotate($degrees) { + if(!$this->is_valid()) + return FALSE; + + if($this->is_imagick()) { + $this->image->setFirstIterator(); + do { + $this->image->rotateImage(new ImagickPixel(), -$degrees); // ImageMagick rotates in the opposite direction of imagerotate() + } while ($this->image->nextImage()); + return; + } + + $this->image = imagerotate($this->image,$degrees,0); + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); + } + + public function flip($horiz = true, $vert = false) { + if(!$this->is_valid()) + return FALSE; + + if($this->is_imagick()) { + $this->image->setFirstIterator(); + do { + if($horiz) $this->image->flipImage(); + if($vert) $this->image->flopImage(); + } while ($this->image->nextImage()); + return; + } + + $w = imagesx($this->image); + $h = imagesy($this->image); + $flipped = imagecreate($w, $h); + if($horiz) { + for ($x = 0; $x < $w; $x++) { + imagecopy($flipped, $this->image, $x, 0, $w - $x - 1, 0, 1, $h); + } + } + if($vert) { + for ($y = 0; $y < $h; $y++) { + imagecopy($flipped, $this->image, 0, $y, 0, $h - $y - 1, $w, 1); + } + } + $this->image = $flipped; + } + + public function orient($filename) { + // based off comment on http://php.net/manual/en/function.imagerotate.php + + if(!$this->is_valid()) + return FALSE; + + if( (! function_exists('exif_read_data')) || ($this->getType() !== 'image/jpeg') ) + return; + + $exif = @exif_read_data($filename); + + if(! $exif) + return; + + $ort = $exif['Orientation']; + + switch($ort) + { + case 1: // nothing + break; + + case 2: // horizontal flip + $this->flip(); + break; + + case 3: // 180 rotate left + $this->rotate(180); + break; + + case 4: // vertical flip + $this->flip(false, true); + break; + + case 5: // vertical flip + 90 rotate right + $this->flip(false, true); + $this->rotate(-90); + break; + + case 6: // 90 rotate right + $this->rotate(-90); + break; + + case 7: // horizontal flip + 90 rotate right + $this->flip(); + $this->rotate(-90); + break; + + case 8: // 90 rotate left + $this->rotate(90); + break; + } + } + + + + public function scaleImageUp($min) { + if(!$this->is_valid()) + return FALSE; + + + $width = $this->getWidth(); + $height = $this->getHeight(); + + $dest_width = $dest_height = 0; + + if((! $width)|| (! $height)) + return FALSE; + + if($width < $min && $height < $min) { + if($width > $height) { + $dest_width = $min; + $dest_height = intval(( $height * $min ) / $width); + } + else { + $dest_width = intval(( $width * $min ) / $height); + $dest_height = $min; + } + } + else { + if( $width < $min ) { + $dest_width = $min; + $dest_height = intval(( $height * $min ) / $width); + } + else { + if( $height < $min ) { + $dest_width = intval(( $width * $min ) / $height); + $dest_height = $min; + } + else { + $dest_width = $width; + $dest_height = $height; + } + } + } + + if($this->is_imagick()) + return $this->scaleImage($dest_width,$dest_height); + + $dest = imagecreatetruecolor( $dest_width, $dest_height ); + imagealphablending($dest, false); + imagesavealpha($dest, true); + if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha + imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height); + if($this->image) + imagedestroy($this->image); + $this->image = $dest; + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); + } + + + + public function scaleImageSquare($dim) { + if(!$this->is_valid()) + return FALSE; + + if($this->is_imagick()) { + $this->image->setFirstIterator(); + do { + $this->image->scaleImage($dim, $dim); + } while ($this->image->nextImage()); + return; + } + + $dest = imagecreatetruecolor( $dim, $dim ); + imagealphablending($dest, false); + imagesavealpha($dest, true); + if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha + imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dim, $dim, $this->width, $this->height); + if($this->image) + imagedestroy($this->image); + $this->image = $dest; + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); + } + + + public function cropImage($max,$x,$y,$w,$h) { + if(!$this->is_valid()) + return FALSE; + + if($this->is_imagick()) { + $this->image->setFirstIterator(); + do { + $this->image->cropImage($w, $h, $x, $y); + /** + * We need to remove the canva, + * or the image is not resized to the crop: + * http://php.net/manual/en/imagick.cropimage.php#97232 + */ + $this->image->setImagePage(0, 0, 0, 0); + } while ($this->image->nextImage()); + return $this->scaleImage($max); } - $s = ob_get_contents(); - ob_end_clean(); - return $s; - } + + $dest = imagecreatetruecolor( $max, $max ); + imagealphablending($dest, false); + imagesavealpha($dest, true); + if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha + imagecopyresampled($dest, $this->image, 0, 0, $x, $y, $max, $max, $w, $h); + if($this->image) + imagedestroy($this->image); + $this->image = $dest; + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); + } + + public function saveImage($path) { + if(!$this->is_valid()) + return FALSE; + + $string = $this->imageString(); + file_put_contents($path, $string); + } + + public function imageString() { + if(!$this->is_valid()) + return FALSE; + + if($this->is_imagick()) { + /* Clean it */ + $this->image = $this->image->deconstructImages(); + $string = $this->image->getImagesBlob(); + return $string; + } + + $quality = FALSE; + + ob_start(); + + switch($this->getType()){ + case "image/png": + $quality = get_config('system','png_quality'); + if((! $quality) || ($quality > 9)) + $quality = PNG_QUALITY; + imagepng($this->image,NULL, $quality); + break; + case "image/jpeg": + $quality = get_config('system','jpeg_quality'); + if((! $quality) || ($quality > 100)) + $quality = JPEG_QUALITY; + imagejpeg($this->image,NULL,$quality); + } + $string = ob_get_contents(); + ob_end_clean(); + + return $string; + } - public function store($uid, $cid, $rid, $filename, $album, $scale, $profile = 0, $allow_cid = '', $allow_gid = '', $deny_cid = '', $deny_gid = '') { + public function store($uid, $cid, $rid, $filename, $album, $scale, $profile = 0, $allow_cid = '', $allow_gid = '', $deny_cid = '', $deny_gid = '') { - $r = q("select `guid` from photo where `resource-id` = '%s' and `guid` != '' limit 1", - dbesc($rid) - ); - if(count($r)) - $guid = $r[0]['guid']; - else - $guid = get_guid(); + $r = q("select `guid` from photo where `resource-id` = '%s' and `guid` != '' limit 1", + dbesc($rid) + ); + if(count($r)) + $guid = $r[0]['guid']; + else + $guid = get_guid(); - $x = q("select id from photo where `resource-id` = '%s' and uid = %d and `contact-id` = %d and `scale` = %d limit 1", - dbesc($rid), - intval($uid), - intval($cid), - intval($scale) - ); - if(count($x)) { - $r = q("UPDATE `photo` - set `uid` = %d, - `contact-id` = %d, - `guid` = '%s', - `resource-id` = '%s', - `created` = '%s', - `edited` = '%s', - `filename` = '%s', - `type` = '%s', - `album` = '%s', - `height` = %d, - `width` = %d, - `data` = '%s', - `scale` = %d, - `profile` = %d, - `allow_cid` = '%s', - `allow_gid` = '%s', - `deny_cid` = '%s', - `deny_gid` = '%s' - where id = %d limit 1", + $x = q("select id from photo where `resource-id` = '%s' and uid = %d and `contact-id` = %d and `scale` = %d limit 1", + dbesc($rid), + intval($uid), + intval($cid), + intval($scale) + ); + if(count($x)) { + $r = q("UPDATE `photo` + set `uid` = %d, + `contact-id` = %d, + `guid` = '%s', + `resource-id` = '%s', + `created` = '%s', + `edited` = '%s', + `filename` = '%s', + `type` = '%s', + `album` = '%s', + `height` = %d, + `width` = %d, + `data` = '%s', + `scale` = %d, + `profile` = %d, + `allow_cid` = '%s', + `allow_gid` = '%s', + `deny_cid` = '%s', + `deny_gid` = '%s' + where id = %d limit 1", - intval($uid), - intval($cid), - dbesc($guid), - dbesc($rid), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - dbesc(basename($filename)), - dbesc($this->type), - dbesc($album), - intval($this->height), - intval($this->width), - dbesc($this->imageString()), - intval($scale), - intval($profile), - dbesc($allow_cid), - dbesc($allow_gid), - dbesc($deny_cid), - dbesc($deny_gid), - intval($x[0]['id']) - ); - } - else { - $r = q("INSERT INTO `photo` - ( `uid`, `contact-id`, `guid`, `resource-id`, `created`, `edited`, `filename`, type, `album`, `height`, `width`, `data`, `scale`, `profile`, `allow_cid`, `allow_gid`, `deny_cid`, `deny_gid` ) - VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', %d, %d, '%s', '%s', '%s', '%s' )", - intval($uid), - intval($cid), - dbesc($guid), - dbesc($rid), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - dbesc(basename($filename)), - dbesc($this->type), - dbesc($album), - intval($this->height), - intval($this->width), - dbesc($this->imageString()), - intval($scale), - intval($profile), - dbesc($allow_cid), - dbesc($allow_gid), - dbesc($deny_cid), - dbesc($deny_gid) - ); - } - return $r; - } + intval($uid), + intval($cid), + dbesc($guid), + dbesc($rid), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(basename($filename)), + dbesc($this->getType()), + dbesc($album), + intval($this->getHeight()), + intval($this->getWidth()), + dbesc($this->imageString()), + intval($scale), + intval($profile), + dbesc($allow_cid), + dbesc($allow_gid), + dbesc($deny_cid), + dbesc($deny_gid), + intval($x[0]['id']) + ); + } + else { + $r = q("INSERT INTO `photo` + ( `uid`, `contact-id`, `guid`, `resource-id`, `created`, `edited`, `filename`, type, `album`, `height`, `width`, `data`, `scale`, `profile`, `allow_cid`, `allow_gid`, `deny_cid`, `deny_gid` ) + VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', %d, %d, '%s', '%s', '%s', '%s' )", + intval($uid), + intval($cid), + dbesc($guid), + dbesc($rid), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(basename($filename)), + dbesc($this->getType()), + dbesc($album), + intval($this->getHeight()), + intval($this->getWidth()), + dbesc($this->imageString()), + intval($scale), + intval($profile), + dbesc($allow_cid), + dbesc($allow_gid), + dbesc($deny_cid), + dbesc($deny_gid) + ); + } + return $r; + } }} /** * Guess image mimetype from filename or from Content-Type header - * + * * @arg $filename string Image filename * @arg $fromcurl boolean Check Content-Type header from curl request */ function guess_image_type($filename, $fromcurl=false) { logger('Photo: guess_image_type: '.$filename . ($fromcurl?' from curl headers':''), LOGGER_DEBUG); - $type = null; - if ($fromcurl) { - $a = get_app(); - $headers=array(); - $h = explode("\n",$a->get_curl_headers()); - foreach ($h as $l) { - list($k,$v) = array_map("trim", explode(":", trim($l), 2)); - $headers[$k] = $v; - } - if (array_key_exists('Content-Type', $headers)) - $type = $headers['Content-Type']; - } - if (is_null($type)){ - $ext = pathinfo($filename, PATHINFO_EXTENSION); - $types = Photo::supportedTypes(); - $type = "image/jpeg"; - foreach ($types as $m=>$e){ - if ($ext==$e) $type = $m; - } - - } + $type = null; + if ($fromcurl) { + $a = get_app(); + $headers=array(); + $h = explode("\n",$a->get_curl_headers()); + foreach ($h as $l) { + list($k,$v) = array_map("trim", explode(":", trim($l), 2)); + $headers[$k] = $v; + } + if (array_key_exists('Content-Type', $headers)) + $type = $headers['Content-Type']; + } + if (is_null($type)){ + // Guessing from extension? Isn't that... dangerous? + if(class_exists('Imagick') && file_exists($filename) && is_readable($filename)) { + /** + * Well, this not much better, + * but at least it comes from the data inside the image, + * we won't be tricked by a manipulated extension + */ + $image = new Imagick($filename); + $type = $image->getImageMimeType(); + } else { + $ext = pathinfo($filename, PATHINFO_EXTENSION); + $types = Photo::supportedTypes(); + $type = "image/jpeg"; + foreach ($types as $m=>$e){ + if ($ext==$e) $type = $m; + } + } + } logger('Photo: guess_image_type: type='.$type, LOGGER_DEBUG); - return $type; - + return $type; + } function import_profile_photo($photo,$uid,$cid) { - $a = get_app(); + $a = get_app(); - $r = q("select `resource-id` from photo where `uid` = %d and `contact-id` = %d and `scale` = 4 and `album` = 'Contact Photos' limit 1", - intval($uid), - intval($cid) - ); - if(count($r)) { - $hash = $r[0]['resource-id']; - } - else { - $hash = photo_new_resource(); - } - - $photo_failure = false; + $r = q("select `resource-id` from photo where `uid` = %d and `contact-id` = %d and `scale` = 4 and `album` = 'Contact Photos' limit 1", + intval($uid), + intval($cid) + ); + if(count($r) && strlen($r[0]['resource-id'])) { + $hash = $r[0]['resource-id']; + } + else { + $hash = photo_new_resource(); + } - $filename = basename($photo); - $img_str = fetch_url($photo,true); - - // guess mimetype from headers or filename - $type = guess_image_type($photo,true); + $photo_failure = false; - - $img = new Photo($img_str, $type); - if($img->is_valid()) { + $filename = basename($photo); + $img_str = fetch_url($photo,true); - $img->scaleImageSquare(175); - - $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 4 ); + $type = guess_image_type($photo,true); + $img = new Photo($img_str, $type); + if($img->is_valid()) { - if($r === false) - $photo_failure = true; + $img->scaleImageSquare(175); - $img->scaleImage(80); + $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 4 ); - $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 5 ); + if($r === false) + $photo_failure = true; - if($r === false) - $photo_failure = true; + $img->scaleImage(80); - $img->scaleImage(48); + $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 5 ); - $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 6 ); + if($r === false) + $photo_failure = true; - if($r === false) - $photo_failure = true; + $img->scaleImage(48); - $photo = $a->get_baseurl() . '/photo/' . $hash . '-4.' . $img->getExt(); - $thumb = $a->get_baseurl() . '/photo/' . $hash . '-5.' . $img->getExt(); - $micro = $a->get_baseurl() . '/photo/' . $hash . '-6.' . $img->getExt(); - } - else - $photo_failure = true; + $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 6 ); - if($photo_failure) { - $photo = $a->get_baseurl() . '/images/person-175.jpg'; - $thumb = $a->get_baseurl() . '/images/person-80.jpg'; - $micro = $a->get_baseurl() . '/images/person-48.jpg'; - } + if($r === false) + $photo_failure = true; - return(array($photo,$thumb,$micro)); + $photo = $a->get_baseurl() . '/photo/' . $hash . '-4.' . $img->getExt(); + $thumb = $a->get_baseurl() . '/photo/' . $hash . '-5.' . $img->getExt(); + $micro = $a->get_baseurl() . '/photo/' . $hash . '-6.' . $img->getExt(); + } + else + $photo_failure = true; + + if($photo_failure) { + $photo = $a->get_baseurl() . '/images/person-175.jpg'; + $thumb = $a->get_baseurl() . '/images/person-80.jpg'; + $micro = $a->get_baseurl() . '/images/person-48.jpg'; + } + + return(array($photo,$thumb,$micro)); } diff --git a/include/Scrape.php b/include/Scrape.php index b784650cd1..7eaac3b449 100644 --- a/include/Scrape.php +++ b/include/Scrape.php @@ -394,7 +394,10 @@ function probe_url($url, $mode = PROBE_NORMAL) { } if($link['@attributes']['rel'] === 'diaspora-public-key') { $diaspora_key = base64_decode(unamp($link['@attributes']['href'])); - $pubkey = rsatopem($diaspora_key); + if(strstr($diaspora_key,'RSA ')) + $pubkey = rsatopem($diaspora_key); + else + $pubkey = $diaspora_key; $diaspora = true; } } @@ -455,10 +458,10 @@ function probe_url($url, $mode = PROBE_NORMAL) { $poll = 'email ' . random_string(); $priority = 0; $x = email_msg_meta($mbox,$msgs[0]); - if(stristr($x->from,$orig_url)) - $adr = imap_rfc822_parse_adrlist($x->from,''); - elseif(stristr($x->to,$orig_url)) - $adr = imap_rfc822_parse_adrlist($x->to,''); + if(stristr($x[0]->from,$orig_url)) + $adr = imap_rfc822_parse_adrlist($x[0]->from,''); + elseif(stristr($x[0]->to,$orig_url)) + $adr = imap_rfc822_parse_adrlist($x[0]->to,''); if(isset($adr)) { foreach($adr as $feadr) { if((strcasecmp($feadr->mailbox,$name) == 0) @@ -551,6 +554,13 @@ function probe_url($url, $mode = PROBE_NORMAL) { logger('probe_url: scrape_vcard: ' . print_r($vcard,true), LOGGER_DATA); } + if($diaspora && $addr) { + // Diaspora returns the name as the nick. As the nick will never be updated, + // let's use the Diaspora nickname (the first part of the handle) as the nick instead + $addr_parts = explode('@', $addr); + $vcard['nick'] = $addr_parts[0]; + } + if($twitter) { logger('twitter: setup'); $tid = basename($url); @@ -560,9 +570,10 @@ function probe_url($url, $mode = PROBE_NORMAL) { else $poll = $tapi . '?screen_name=' . $tid; $profile = 'http://twitter.com/#!/' . $tid; - $vcard['photo'] = 'https://api.twitter.com/1/users/profile_image/' . $tid; + //$vcard['photo'] = 'https://api.twitter.com/1/users/profile_image/' . $tid; + $vcard['photo'] = 'https://api.twitter.com/1/users/profile_image?screen_name=' . $tid . '&size=bigger'; $vcard['nick'] = $tid; - $vcard['fn'] = $tid . '@twitter'; + $vcard['fn'] = $tid; } if($lastfm) { diff --git a/include/api.php b/include/api.php index d790b4b875..456d984de1 100644 --- a/include/api.php +++ b/include/api.php @@ -156,6 +156,7 @@ //echo "
"; var_dump($r); die();
 			}
 		}
+		header("HTTP/1.1 404 Not Found");
 		logger('API call not implemented: '.$a->query_string." - ".print_r($_REQUEST,true));
 		$r = 'not implemented';
 		switch($type){
@@ -490,7 +491,8 @@
                 $_REQUEST['type'] = 'wall';
                 $_REQUEST['profile_uid'] = local_user();
                 $_REQUEST['api_source'] = true;
-                $txt = urldecode(requestdata('status'));
+                $txt = requestdata('status');
+                //$txt = urldecode(requestdata('status'));
 
                 require_once('library/HTMLPurifier.auto.php');
                 require_once('include/html2bbcode.php');
@@ -554,7 +556,8 @@
 
 		}
 		else
-			$_REQUEST['body'] = urldecode(requestdata('status'));
+			$_REQUEST['body'] = requestdata('status');
+			//$_REQUEST['body'] = urldecode(requestdata('status'));
 
 		$parent = requestdata('in_reply_to_status_id');
 		if(ctype_digit($parent))
@@ -752,6 +755,15 @@
 
 		$ret = api_format_items($r,$user_info);
 
+		// We aren't going to try to figure out at the item, group, and page
+		// level which items you've seen and which you haven't. If you're looking
+		// at the network timeline just mark everything seen. 
+	
+		$r = q("UPDATE `item` SET `unseen` = 0 
+			WHERE `unseen` = 1 AND `uid` = %d",
+			intval($user_info['uid'])
+		);
+
 
 		$data = array('$statuses' => $ret);
 		switch($type){
@@ -1725,4 +1737,6 @@ notifications/follow
 notifications/leave
 blocks/exists
 blocks/blocking
+lists
 */
+
diff --git a/include/auth.php b/include/auth.php
index cba6a67a7f..c4f1f08651 100644
--- a/include/auth.php
+++ b/include/auth.php
@@ -10,14 +10,13 @@ function nuke_session() {
 	unset($_SESSION['administrator']);
 	unset($_SESSION['cid']);
 	unset($_SESSION['theme']);
+	unset($_SESSION['mobile-theme']);
 	unset($_SESSION['page_flags']);
 	unset($_SESSION['submanage']);
 	unset($_SESSION['my_url']);
 	unset($_SESSION['my_address']);
 	unset($_SESSION['addr']);
 	unset($_SESSION['return_url']);
-	unset($_SESSION['theme']);
-	unset($_SESSION['page_flags']);
 }
 
 
@@ -169,23 +168,4 @@ else {
 	}
 }
 
-// Returns an array of group id's this contact is a member of.
-// This array will only contain group id's related to the uid of this
-// DFRN contact. They are *not* neccessarily unique across the entire site. 
-
-
-if(! function_exists('init_groups_visitor')) {
-function init_groups_visitor($contact_id) {
-	$groups = array();
-	$r = q("SELECT `gid` FROM `group_member` 
-		WHERE `contact-id` = %d ",
-		intval($contact_id)
-	);
-	if(count($r)) {
-		foreach($r as $rr)
-			$groups[] = $rr['gid'];
-	}
-	return $groups;
-}}
-
 
diff --git a/include/bb2diaspora.php b/include/bb2diaspora.php
index d0cb60302c..75fe1ef35d 100644
--- a/include/bb2diaspora.php
+++ b/include/bb2diaspora.php
@@ -7,6 +7,7 @@ require_once("include/html2bbcode.php");
 require_once("include/bbcode.php");
 require_once("include/markdownify/markdownify.php");
 
+
 // we don't want to support a bbcode specific markdown interpreter
 // and the markdown library we have is pretty good, but provides HTML output.
 // So we'll use that to convert to HTML, then convert the HTML back to bbcode,
@@ -51,10 +52,10 @@ function diaspora2bb($s) {
 	$s = preg_replace("/([^\]\=]|^)(https?\:\/\/)([a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", '$1[url=$2$3]$2$3[/url]',$s);
 
 	//$s = preg_replace("/([^\]\=]|^)(https?\:\/\/)(vimeo|youtu|www\.youtube|soundcloud)([a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", '$1[url=$2$3$4]$2$3$4[/url]',$s);
-	$s = preg_replace("/\[url\=?(.*?)\]https?:\/\/www.youtube.com\/watch\?v\=(.*?)\[\/url\]/ism",'[youtube]$2[/youtube]',$s);
-	$s = preg_replace("/\[url\=https?:\/\/www.youtube.com\/watch\?v\=(.*?)\].*?\[\/url\]/ism",'[youtube]$1[/youtube]',$s);
-	$s = preg_replace("/\[url\=?(.*?)\]https?:\/\/vimeo.com\/([0-9]+)(.*?)\[\/url\]/ism",'[vimeo]$2[/vimeo]',$s);
-	$s = preg_replace("/\[url\=https?:\/\/vimeo.com\/([0-9]+)\](.*?)\[\/url\]/ism",'[vimeo]$1[/vimeo]',$s);
+	$s = bb_tag_preg_replace("/\[url\=?(.*?)\]https?:\/\/www.youtube.com\/watch\?v\=(.*?)\[\/url\]/ism",'[youtube]$2[/youtube]','url',$s);
+	$s = bb_tag_preg_replace("/\[url\=https?:\/\/www.youtube.com\/watch\?v\=(.*?)\].*?\[\/url\]/ism",'[youtube]$1[/youtube]','url',$s);
+	$s = bb_tag_preg_replace("/\[url\=?(.*?)\]https?:\/\/vimeo.com\/([0-9]+)(.*?)\[\/url\]/ism",'[vimeo]$2[/vimeo]','url',$s);
+	$s = bb_tag_preg_replace("/\[url\=https?:\/\/vimeo.com\/([0-9]+)\](.*?)\[\/url\]/ism",'[vimeo]$1[/vimeo]','url',$s);
 	// remove duplicate adjacent code tags
 	$s = preg_replace("/(\[code\])+(.*?)(\[\/code\])+/ism","[code]$2[/code]", $s);
 
@@ -130,63 +131,64 @@ function diaspora_ol($s) {
 }
 
 
-function bb2diaspora($Text,$preserve_nl = false) {
+function bb2diaspora($Text,$preserve_nl = false, $fordiaspora = true) {
 
-//////////////////////
-// An attempt was made to convert bbcode to html and then to markdown
-// consisting of the following lines.
-// I'm undoing this as we have a lot of bbcode constructs which
-// were simply getting lost, for instance bookmark, vimeo, video, youtube, events, etc.
-// We can try this again, but need a very good test sequence to verify
-// all the major bbcode constructs that we use are getting through.
-//////////////////////
-/*
-	// bbcode() will convert "[*]" into "
  • " with no closing "
  • " - // Markdownify() is unable to handle these, as it makes each new - // "
  • " into a deeper nested element until it crashes. So pre-format - // the lists as Diaspora lists before sending the $Text to bbcode() - // - // Note that to get nested lists to work for Diaspora, we would need - // to define the closing tag for the list elements. So nested lists - // are going to be flattened out in Diaspora for now + // Re-enabling the converter again. + // The bbcode parser now handles youtube-links (and the other stuff) correctly. + // Additionally the html code is now fixed so that lists are now working. - $endlessloop = 0; - while ((((strpos($Text, "[/list]") !== false) && (strpos($Text, "[list") !== false)) || - ((strpos($Text, "[/ol]") !== false) && (strpos($Text, "[ol]") !== false)) || - ((strpos($Text, "[/ul]") !== false) && (strpos($Text, "[ul]") !== false))) && (++$endlessloop < 20)) { - $Text = preg_replace_callback("/\[list\](.*?)\[\/list\]/is", 'diaspora_ul', $Text); - $Text = preg_replace_callback("/\[list=1\](.*?)\[\/list\]/is", 'diaspora_ol', $Text); - $Text = preg_replace_callback("/\[list=i\](.*?)\[\/list\]/s",'diaspora_ol', $Text); - $Text = preg_replace_callback("/\[list=I\](.*?)\[\/list\]/s", 'diaspora_ol', $Text); - $Text = preg_replace_callback("/\[list=a\](.*?)\[\/list\]/s", 'diaspora_ol', $Text); - $Text = preg_replace_callback("/\[list=A\](.*?)\[\/list\]/s", 'diaspora_ol', $Text); - $Text = preg_replace_callback("/\[ul\](.*?)\[\/ul\]/is", 'diaspora_ul', $Text); - $Text = preg_replace_callback("/\[ol\](.*?)\[\/ol\]/is", 'diaspora_ol', $Text); - } + /** + * Transform #tags, strip off the [url] and replace spaces with underscore + */ + $Text = preg_replace_callback('/#\[url\=(\w+.*?)\](\w+.*?)\[\/url\]/i', create_function('$match', + 'return \'#\'. str_replace(\' \', \'_\', $match[2]);' + ), $Text); -*/ + + // Converting images with size parameters to simple images. Markdown doesn't know it. + $Text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $Text); + + // the following was added on 10-January-2012 due to an inability of Diaspora's + // new javascript markdown processor to handle links with images as the link "text" + // It is not optimal and may be removed if this ability is restored in the future + //if ($fordiaspora) + // $Text = preg_replace("/\[url\=([^\[\]]*)\]\s*\[img\](.*?)\[\/img\]\s*\[\/url\]/ism", + // "[url]$1[/url]\n[img]$2[/img]", $Text); // Convert it to HTML - don't try oembed -// $Text = bbcode($Text, $preserve_nl, false); + $Text = bbcode($Text, $preserve_nl, false); // Now convert HTML to Markdown -// $md = new Markdownify(false, false, false); -// $Text = $md->parseString($Text); + $md = new Markdownify(false, false, false); + $Text = $md->parseString($Text); + + // The Markdownify converter converts underscores '_' in URLs to '\_', which + // messes up the URL. Manually fix these + $count = 1; + $pos = bb_find_open_close($Text, '[', ']', $count); + while($pos !== false) { + $start = substr($Text, 0, $pos['start']); + $subject = substr($Text, $pos['start'], $pos['end'] - $pos['start'] + 1); + $end = substr($Text, $pos['end'] + 1); + + $subject = str_replace('\_', '_', $subject); + $Text = $start . $subject . $end; + + $count++; + $pos = bb_find_open_close($Text, '[', ']', $count); + } // If the text going into bbcode() has a plain URL in it, i.e. // with no [url] tags around it, it will come out of parseString() // looking like: , which gets removed by strip_tags(). // So take off the angle brackets of any such URL -// $Text = preg_replace("//is", "http$1", $Text); + $Text = preg_replace("//is", "http$1", $Text); // Remove all unconverted tags -// $Text = strip_tags($Text); - -////// -// end of bb->html->md conversion attempt -////// + $Text = strip_tags($Text); +/* Old routine $ev = bbtoevent($Text); @@ -362,6 +364,7 @@ function bb2diaspora($Text,$preserve_nl = false) { $Text = preg_replace_callback('/\[(.*?)\]\((.*?)\)/ism','unescape_underscores_in_links',$Text); +*/ // Remove any leading or trailing whitespace, as this will mess up // the Diaspora signature verification and cause the item to disappear diff --git a/include/bbcode.php b/include/bbcode.php index 63dd9695e7..ef4a9aa9ba 100644 --- a/include/bbcode.php +++ b/include/bbcode.php @@ -47,6 +47,89 @@ function bb_unspacefy_and_trim($st) { return $unspacefied; } +function bb_find_open_close($s, $open, $close, $occurance = 1) { + + if($occurance < 1) + $occurance = 1; + + $start_pos = -1; + for($i = 1; $i <= $occurance; $i++) { + if( $start_pos !== false) + $start_pos = strpos($s, $open, $start_pos + 1); + } + + if( $start_pos === false) + return false; + + $end_pos = strpos($s, $close, $start_pos); + + if( $end_pos === false) + return false; + + $res = array( 'start' => $start_pos, 'end' => $end_pos ); + + return $res; +} + +function get_bb_tag_pos($s, $name, $occurance = 1) { + + if($occurance < 1) + $occurance = 1; + + $start_open = -1; + for($i = 1; $i <= $occurance; $i++) { + if( $start_open !== false) + $start_open = strpos($s, '[' . $name, $start_open + 1); // allow [name= type tags + } + + if( $start_open === false) + return false; + + $start_equal = strpos($s, '=', $start_open); + $start_close = strpos($s, ']', $start_open); + + if( $start_close === false) + return false; + + $start_close++; + + $end_open = strpos($s, '[/' . $name . ']', $start_close); + + if( $end_open === false) + return false; + + $res = array( 'start' => array('open' => $start_open, 'close' => $start_close), + 'end' => array('open' => $end_open, 'close' => $end_open + strlen('[/' . $name . ']')) ); + if( $start_equal !== false) + $res['start']['equal'] = $start_equal + 1; + + return $res; +} + +function bb_tag_preg_replace($pattern, $replace, $name, $s) { + + $string = $s; + + $occurance = 1; + $pos = get_bb_tag_pos($string, $name, $occurance); + while($pos !== false && $occurance < 1000) { + + $start = substr($string, 0, $pos['start']['open']); + $subject = substr($string, $pos['start']['open'], $pos['end']['close'] - $pos['start']['open']); + $end = substr($string, $pos['end']['close']); + if($end === false) + $end = ''; + + $subject = preg_replace($pattern, $replace, $subject); + $string = $start . $subject . $end; + + $occurance++; + $pos = get_bb_tag_pos($string, $name, $occurance); + } + + return $string; +} + if(! function_exists('bb_extract_images')) { function bb_extract_images($body) { @@ -123,6 +206,10 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { $Text = preg_replace_callback("/\[pre\](.*?)\[\/pre\]/ism", 'bb_spacefy',$Text); + // Move all spaces out of the tags + $Text = preg_replace("/\[(\w*)\](\s*)/ism", '$2[$1]', $Text); + $Text = preg_replace("/(\s*)\[\/(\w*)\]/ism", '[/$2]$1', $Text); + // Extract the private images which use data url's since preg has issues with // large data sizes. Stash them away while we do bbcode conversion, and then put them back // in after we've done all the regex matching. We cannot use any preg functions to do this. @@ -220,6 +307,12 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { // Check for list text $Text = str_replace("[*]", "
  • ", $Text); + // Check for style sheet commands + $Text = preg_replace("(\[style=(.*?)\](.*?)\[\/style\])ism","$2",$Text); + + // Check for CSS classes + $Text = preg_replace("(\[class=(.*?)\](.*?)\[\/class\])ism","$2",$Text); + // handle nested lists $endlessloop = 0; @@ -313,21 +406,30 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { $Text = preg_replace("/\[img\](.*?)\[\/img\]/ism", '' . t('Image/photo') . '', $Text); - $Text = preg_replace("/\[video\](.*?\.(ogg|ogv|oga|ogm|webm|mp4))\[\/video\]/ism", '', $Text); - $Text = preg_replace("/\[audio\](.*?\.(ogg|ogv|oga|ogm|webm|mp4|mp3))\[\/audio\]/ism", '', $Text); + $Text = preg_replace("/\[crypt\](.*?)\[\/crypt\]/ism",'
    ' . t('Encrypted content') . '
    ', $Text); + $Text = preg_replace("/\[crypt=(.*?)\](.*?)\[\/crypt\]/ism",'
    ' . t('Encrypted content') . '
    ', $Text); + // Try to Oembed if ($tryoembed) { + $Text = preg_replace("/\[video\](.*?\.(ogg|ogv|oga|ogm|webm|mp4))\[\/video\]/ism", '', $Text); + $Text = preg_replace("/\[audio\](.*?\.(ogg|ogv|oga|ogm|webm|mp4|mp3))\[\/audio\]/ism", '', $Text); + $Text = preg_replace_callback("/\[video\](.*?)\[\/video\]/ism", 'tryoembed', $Text); $Text = preg_replace_callback("/\[audio\](.*?)\[\/audio\]/ism", 'tryoembed', $Text); + } else { + $Text = preg_replace("/\[video\](.*?)\[\/video\]/", '$1', $Text); + $Text = preg_replace("/\[audio\](.*?)\[\/audio\]/", '$1', $Text); } // html5 video and audio - $Text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '', $Text); - + if ($tryoembed) + $Text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '', $Text); + else + $Text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '$1', $Text); // Youtube extensions if ($tryoembed) { @@ -340,7 +442,10 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { $Text = preg_replace("/\[youtube\]https?:\/\/www.youtube.com\/embed\/(.*?)\[\/youtube\]/ism",'[youtube]$1[/youtube]',$Text); $Text = preg_replace("/\[youtube\]https?:\/\/youtu.be\/(.*?)\[\/youtube\]/ism",'[youtube]$1[/youtube]',$Text); - $Text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", '', $Text); + if ($tryoembed) + $Text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", '', $Text); + else + $Text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", "http://www.youtube.com/watch?v=$1", $Text); if ($tryoembed) { @@ -349,8 +454,12 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { } $Text = preg_replace("/\[vimeo\]https?:\/\/player.vimeo.com\/video\/([0-9]+)(.*?)\[\/vimeo\]/ism",'[vimeo]$1[/vimeo]',$Text); - $Text = preg_replace("/\[vimeo\]https?:\/\/vimeo.com\/([0-9]+)(.*?)\[\/vimeo\]/ism",'[vimeo]$1[/vimeo]',$Text); - $Text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", '', $Text); + $Text = preg_replace("/\[vimeo\]https?:\/\/vimeo.com\/([0-9]+)(.*?)\[\/vimeo\]/ism",'[vimeo]$1[/vimeo]',$Text); + + if ($tryoembed) + $Text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", '', $Text); + else + $Text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", "http://vimeo.com/$1", $Text); // $Text = preg_replace("/\[youtube\](.*?)\[\/youtube\]/", '', $Text); @@ -358,6 +467,9 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { // oembed tag $Text = oembed_bbcode2html($Text); + // Avoid triple linefeeds through oembed + $Text = str_replace("


    ", "

    ", $Text); + // If we found an event earlier, strip out all the event code and replace with a reformatted version. // Replace the event-start section with the entire formatted event. The other bbcode is stripped. // Summary (e.g. title) is required, earlier revisions only required description (in addition to @@ -383,13 +495,35 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { $Text = preg_replace('/\[\&\;([#a-z0-9]+)\;\]/','&$1;',$Text); + $Text = preg_replace('/\&\#039\;/','\'',$Text); + $Text = preg_replace('/\"\;/','"',$Text); // fix any escaped ampersands that may have been converted into links $Text = preg_replace("/\<(.*?)(src|href)=(.*?)\&\;(.*?)\>/ism",'<$1$2=$3&$4>',$Text); + $Text = preg_replace("/\<(.*?)(src|href)=\"[^hfm](.*?)\>/ism",'<$1$2="">',$Text); if($saved_image) $Text = bb_replace_images($Text, $saved_image); + // Clean up the HTML by loading and saving the HTML with the DOM + // Only do it when it has to be done - for performance reasons + if (!$tryoembed) { + $doc = new DOMDocument(); + $doc->preserveWhiteSpace = false; + + $Text = mb_convert_encoding($Text, 'HTML-ENTITIES', "UTF-8"); + + $doctype = ''; + @$doc->loadHTML($doctype."".$Text.""); + + $Text = $doc->saveHTML(); + $Text = str_replace(array("", "", $doctype), array("", "", ""), $Text); + + $Text = str_replace('
  • ','', $Text); + + $Text = mb_convert_encoding($Text, "UTF-8", 'HTML-ENTITIES'); + } + call_hooks('bbcode',$Text); return $Text; diff --git a/include/config.php b/include/config.php index 1f2a70e5a1..44606e329a 100644 --- a/include/config.php +++ b/include/config.php @@ -68,7 +68,7 @@ function get_config($family, $key, $instore = false) { ); if(count($ret)) { // manage array value - $val = (preg_match("|^a:[0-9]+:{.*}$|", $ret[0]['v'])?unserialize( $ret[0]['v']):$ret[0]['v']); + $val = (preg_match("|^a:[0-9]+:{.*}$|s", $ret[0]['v'])?unserialize( $ret[0]['v']):$ret[0]['v']); $a->config[$family][$key] = $val; return $val; } @@ -162,7 +162,7 @@ function get_pconfig($uid,$family, $key, $instore = false) { ); if(count($ret)) { - $val = (preg_match("|^a:[0-9]+:{.*}$|", $ret[0]['v'])?unserialize( $ret[0]['v']):$ret[0]['v']); + $val = (preg_match("|^a:[0-9]+:{.*}$|s", $ret[0]['v'])?unserialize( $ret[0]['v']):$ret[0]['v']); $a->config[$uid][$family][$key] = $val; return $val; } diff --git a/include/contact_widgets.php b/include/contact_widgets.php index ce1cdbad55..ea71b3b707 100644 --- a/include/contact_widgets.php +++ b/include/contact_widgets.php @@ -142,9 +142,16 @@ function common_friends_visitor_widget($profile_uid) { $cid = $zcid = 0; - if(can_write_wall($a,$profile_uid)) - $cid = remote_user(); - else { + if(is_array($_SESSION['remote'])) { + foreach($_SESSION['remote'] as $visitor) { + if($visitor['uid'] == $profile_uid) { + $cid = $visitor['cid']; + break; + } + } + } + + if(! $cid) { if(get_my_url()) { $r = q("select id from contact where nurl = '%s' and uid = %d limit 1", dbesc(normalise_link(get_my_url())), diff --git a/include/conversation.php b/include/conversation.php index 240cd374f3..43d20a4014 100644 --- a/include/conversation.php +++ b/include/conversation.php @@ -1,5 +1,8 @@ "; - if ($item['verb']=== ACTIVITY_LIKE || $item['verb']=== ACTIVITY_DISLIKE){ + if (activity_match($item['verb'],ACTIVITY_LIKE) || activity_match($item['verb'],ACTIVITY_DISLIKE)){ - $r = q("SELECT * from `item`,`contact` WHERE + $r = q("SELECT * from `item`,`contact` WHERE `item`.`contact-id`=`contact`.`id` AND `item`.`uri`='%s';", dbesc($item['parent-uri'])); if(count($r)==0) return; $obj=$r[0]; - - $author = '[url=' . $item['author-link'] . ']' . $item['author-name'] . '[/url]'; + + $author = '[url=' . $item['author-link'] . ']' . $item['author-name'] . '[/url]'; $objauthor = '[url=' . $obj['author-link'] . ']' . $obj['author-name'] . '[/url]'; - + switch($obj['verb']){ case ACTIVITY_POST: switch ($obj['object-type']){ @@ -113,38 +123,36 @@ function localize_item(&$item){ default: if($obj['resource-id']){ $post_type = t('photo'); - $m=array(); preg_match("/\[url=([^]]*)\]/", $obj['body'], $m); + $m=array(); preg_match("/\[url=([^]]*)\]/", $obj['body'], $m); $rr['plink'] = $m[1]; } else { $post_type = t('status'); } } - + $plink = '[url=' . $obj['plink'] . ']' . $post_type . '[/url]'; - - switch($item['verb']){ - case ACTIVITY_LIKE : - $bodyverb = t('%1$s likes %2$s\'s %3$s'); - break; - case ACTIVITY_DISLIKE: - $bodyverb = t('%1$s doesn\'t like %2$s\'s %3$s'); - break; + + if(activity_match($item['verb'],ACTIVITY_LIKE)) { + $bodyverb = t('%1$s likes %2$s\'s %3$s'); + } + elseif(activity_match($item['verb'],ACTIVITY_DISLIKE)) { + $bodyverb = t('%1$s doesn\'t like %2$s\'s %3$s'); } $item['body'] = sprintf($bodyverb, $author, $objauthor, $plink); - + } - if ($item['verb']=== ACTIVITY_FRIEND){ + if (activity_match($item['verb'],ACTIVITY_FRIEND)) { if ($item['object-type']=="" || $item['object-type']!== ACTIVITY_OBJ_PERSON) return; $Aname = $item['author-name']; $Alink = $item['author-link']; - + $xmlhead="<"."?xml version='1.0' encoding='UTF-8' ?".">"; - + $obj = parse_xml_string($xmlhead.$item['object']); $links = parse_xml_string($xmlhead."".unxmlify($obj->link).""); - + $Bname = $obj->title; $Blink = ""; $Bphoto = ""; foreach ($links->link as $l){ @@ -153,9 +161,9 @@ function localize_item(&$item){ case "alternate": $Blink = $atts['href']; case "photo": $Bphoto = $atts['href']; } - + } - + $A = '[url=' . zrl($Alink) . ']' . $Aname . '[/url]'; $B = '[url=' . zrl($Blink) . ']' . $Bname . '[/url]'; if ($Bphoto!="") $Bphoto = '[url=' . zrl($Blink) . '][img]' . $Bphoto . '[/img][/url]'; @@ -163,16 +171,73 @@ function localize_item(&$item){ $item['body'] = sprintf( t('%1$s is now friends with %2$s'), $A, $B)."\n\n\n".$Bphoto; } - if ($item['verb']===ACTIVITY_TAG){ - $r = q("SELECT * from `item`,`contact` WHERE + if (stristr($item['verb'],ACTIVITY_POKE)) { + $verb = urldecode(substr($item['verb'],strpos($item['verb'],'#')+1)); + if(! $verb) + return; + if ($item['object-type']=="" || $item['object-type']!== ACTIVITY_OBJ_PERSON) return; + + $Aname = $item['author-name']; + $Alink = $item['author-link']; + + $xmlhead="<"."?xml version='1.0' encoding='UTF-8' ?".">"; + + $obj = parse_xml_string($xmlhead.$item['object']); + $links = parse_xml_string($xmlhead."".unxmlify($obj->link).""); + + $Bname = $obj->title; + $Blink = ""; $Bphoto = ""; + foreach ($links->link as $l){ + $atts = $l->attributes(); + switch($atts['rel']){ + case "alternate": $Blink = $atts['href']; + case "photo": $Bphoto = $atts['href']; + } + + } + + $A = '[url=' . zrl($Alink) . ']' . $Aname . '[/url]'; + $B = '[url=' . zrl($Blink) . ']' . $Bname . '[/url]'; + if ($Bphoto!="") $Bphoto = '[url=' . zrl($Blink) . '][img=80x80]' . $Bphoto . '[/img][/url]'; + + // we can't have a translation string with three positions but no distinguishable text + // So here is the translate string. + + $txt = t('%1$s poked %2$s'); + + // now translate the verb + + $txt = str_replace( t('poked'), t($verb), $txt); + + // then do the sprintf on the translation string + + $item['body'] = sprintf($txt, $A, $B). "\n\n\n" . $Bphoto; + + } + if (stristr($item['verb'],ACTIVITY_MOOD)) { + $verb = urldecode(substr($item['verb'],strpos($item['verb'],'#')+1)); + if(! $verb) + return; + + $Aname = $item['author-name']; + $Alink = $item['author-link']; + $A = '[url=' . zrl($Alink) . ']' . $Aname . '[/url]'; + + $txt = t('%1$s is currently %2$s'); + + $item['body'] = sprintf($txt, $A, t($verb)); + } + + if (activity_match($item['verb'],ACTIVITY_TAG)) { + $r = q("SELECT * from `item`,`contact` WHERE `item`.`contact-id`=`contact`.`id` AND `item`.`uri`='%s';", dbesc($item['parent-uri'])); if(count($r)==0) return; $obj=$r[0]; - - $author = '[url=' . zrl($item['author-link']) . ']' . $item['author-name'] . '[/url]'; + + $author = '[url=' . zrl($item['author-link']) . ']' . $item['author-name'] . '[/url]'; $objauthor = '[url=' . zrl($obj['author-link']) . ']' . $obj['author-name'] . '[/url]'; - + switch($obj['verb']){ case ACTIVITY_POST: switch ($obj['object-type']){ @@ -186,30 +251,30 @@ function localize_item(&$item){ default: if($obj['resource-id']){ $post_type = t('photo'); - $m=array(); preg_match("/\[url=([^]]*)\]/", $obj['body'], $m); + $m=array(); preg_match("/\[url=([^]]*)\]/", $obj['body'], $m); $rr['plink'] = $m[1]; } else { $post_type = t('status'); } } $plink = '[url=' . $obj['plink'] . ']' . $post_type . '[/url]'; - + $parsedobj = parse_xml_string($xmlhead.$item['object']); - + $tag = sprintf('#[url=%s]%s[/url]', $parsedobj->id, $parsedobj->content); $item['body'] = sprintf( t('%1$s tagged %2$s\'s %3$s with %4$s'), $author, $objauthor, $plink, $tag ); - + } - if ($item['verb']=== ACTIVITY_FAVORITE){ + if (activity_match($item['verb'],ACTIVITY_FAVORITE)){ if ($item['object-type']== "") return; $Aname = $item['author-name']; $Alink = $item['author-link']; - + $xmlhead="<"."?xml version='1.0' encoding='UTF-8' ?".">"; - + $obj = parse_xml_string($xmlhead.$item['object']); if(strlen($obj->id)) { $r = q("select * from item where uri = '%s' and uid = %d limit 1", @@ -235,23 +300,67 @@ function localize_item(&$item){ $item['body'] = str_replace($mtch[0],'@[url=' . zrl($mtch[1]). ']',$item['body']); } } + // add zrl's to public images - if(preg_match_all('/\[url=(.*?)\/photos\/(.*?)\/image\/(.*?)\]\[img(.*?)\]h(.*?)\[\/img\]\[\/url\]/is',$item['body'],$matches,PREG_SET_ORDER)) { - foreach($matches as $mtch) { - $item['body'] = str_replace($mtch[0],'[url=' . zrl($mtch[1] . '/photos/' . $mtch[2] . '/image/' . $mtch[3] ,true) . '][img' . $mtch[4] . ']h' . $mtch[5] . '[/img][/url]',$item['body']); - } + $photo_pattern = "/\[url=(.*?)\/photos\/(.*?)\/image\/(.*?)\]\[img(.*?)\]h(.*?)\[\/img\]\[\/url\]/is"; + if(preg_match($photo_pattern,$item['body'])) { + $photo_replace = '[url=' . zrl('$1' . '/photos/' . '$2' . '/image/' . '$3' ,true) . '][img' . '$4' . ']h' . '$5' . '[/img][/url]'; + $item['body'] = bb_tag_preg_replace($photo_pattern, $photo_replace, 'url', $item['body']); + } + + // add sparkle links to appropriate permalinks + + $x = stristr($item['plink'],'/display/'); + if($x) { + $sparkle = false; + $y = best_link_url($item,$sparkle,true); + if(strstr($y,'/redir/')) + $item['plink'] = $y . '?f=&url=' . $item['plink']; } + } +/** + * Count the total of comments on this item and its desendants + */ +function count_descendants($item) { + $total = count($item['children']); + + if($total > 0) { + foreach($item['children'] as $child) { + if(! visible_activity($child)) + $total --; + $total += count_descendants($child); + } + } + + return $total; +} + +function visible_activity($item) { + + if(activity_match($item['verb'],ACTIVITY_LIKE) || activity_match($item['verb'],ACTIVITY_DISLIKE)) + return false; + + if(activity_match($item['verb'],ACTIVITY_FOLLOW) && $item['object-type'] === ACTIVITY_OBJ_NOTE) { + if(! (($item['self']) && ($item['uid'] == local_user()))) { + return false; + } + } + + return true; +} + + /** * "Render" a conversation or list of items for HTML display. * There are two major forms of display: * - Sequential or unthreaded ("New Item View" or search results) * - conversation view * The $mode parameter decides between the various renderings and also - * figures out how to determine page owner and other contextual items + * figures out how to determine page owner and other contextual items * that are based on unique features of the calling module. * */ @@ -265,34 +374,88 @@ function conversation(&$a, $items, $mode, $update, $preview = false) { $ssl_state = ((local_user()) ? true : false); $profile_owner = 0; - $page_writeable = false; + $page_writeable = false; + $live_update_div = ''; $previewing = (($preview) ? ' preview ' : ''); if($mode === 'network') { $profile_owner = local_user(); $page_writeable = true; - } + if(!$update) { + // The special div is needed for liveUpdate to kick in for this page. + // We only launch liveUpdate if you aren't filtering in some incompatible + // way and also you aren't writing a comment (discovered in javascript). - if($mode === 'profile') { + $live_update_div = '
    ' . "\r\n" + . "\r\n"; + } + } + else if($mode === 'profile') { $profile_owner = $a->profile['profile_uid']; $page_writeable = can_write_wall($a,$profile_owner); - } - if($mode === 'notes') { + if(!$update) { + $tab = notags(trim($_GET['tab'])); + $tab = ( $tab ? $tab : 'posts' ); + if($tab === 'posts') { + // This is ugly, but we can't pass the profile_uid through the session to the ajax updater, + // because browser prefetching might change it on us. We have to deliver it with the page. + + $live_update_div = '
    ' . "\r\n" + . "\r\n"; + } + } + } + else if($mode === 'notes') { $profile_owner = local_user(); $page_writeable = true; + if(!$update) { + $live_update_div = '
    ' . "\r\n" + . "\r\n"; + } } - - if($mode === 'display') { + else if($mode === 'display') { $profile_owner = $a->profile['uid']; $page_writeable = can_write_wall($a,$profile_owner); + if(!$update) { + $live_update_div = '
    ' . "\r\n" + . ""; + } } - - if($mode === 'community') { + else if($mode === 'community') { $profile_owner = 0; $page_writeable = false; + if(!$update) { + $live_update_div = '
    ' . "\r\n" + . "\r\n"; + } } + else if($mode === 'search') { + $live_update_div = '' . "\r\n"; + } + + $page_dropping = ((local_user() && local_user() == $profile_owner) ? true : false); + if($update) $return_url = $_SESSION['return_url']; @@ -307,26 +470,26 @@ function conversation(&$a, $items, $mode, $update, $preview = false) { $items = $cb['items']; $cmnt_tpl = get_markup_template('comment_item.tpl'); - $tpl = 'wall_item.tpl'; - $wallwall = 'wallwall_item.tpl'; $hide_comments_tpl = get_markup_template('hide_comments.tpl'); $alike = array(); $dlike = array(); - - + + // array with html for each thread (parent+comments) $threads = array(); $threadsid = -1; - + + $page_template = get_markup_template("conversation.tpl"); + if($items && count($items)) { if($mode === 'network-new' || $mode === 'search' || $mode === 'community') { - // "New Item View" on network page or search page results + // "New Item View" on network page or search page results // - just loop through the items and format them minimally for display - //$tpl = get_markup_template('search_item.tpl'); +// $tpl = get_markup_template('search_item.tpl'); $tpl = 'search_item.tpl'; foreach($items as $item) { @@ -339,24 +502,39 @@ function conversation(&$a, $items, $mode, $update, $preview = false) { $sparkle = ''; if($mode === 'search' || $mode === 'community') { - if(((activity_match($item['verb'],ACTIVITY_LIKE)) || (activity_match($item['verb'],ACTIVITY_DISLIKE))) + if(((activity_match($item['verb'],ACTIVITY_LIKE)) || (activity_match($item['verb'],ACTIVITY_DISLIKE))) && ($item['id'] != $item['parent'])) continue; $nickname = $item['nickname']; } else $nickname = $a->user['nickname']; - + // prevent private email from leaking. if($item['network'] === NETWORK_MAIL && local_user() != $item['uid']) continue; - + $profile_name = ((strlen($item['author-name'])) ? $item['author-name'] : $item['name']); if($item['author-link'] && (! $item['author-name'])) $profile_name = $item['author-link']; + $tags=array(); + $hashtags = array(); + $mentions = array(); + foreach(explode(',',$item['tag']) as $tag){ + $tag = trim($tag); + if ($tag!="") { + $t = bbcode($tag); + $tags[] = $t; + if($t[0] == '#') + $hashtags[] = $t; + elseif($t[0] == '@') + $mentions[] = $t; + } + } + $sp = false; $profile_link = best_link_url($item,$sp); if($profile_link === 'mailbox') @@ -364,7 +542,7 @@ function conversation(&$a, $items, $mode, $update, $preview = false) { if($sp) $sparkle = ' sparkle'; else - $profile_link = zrl($profile_link); + $profile_link = zrl($profile_link); $normalised = normalise_link((strlen($item['author-link'])) ? $item['author-link'] : $item['url']); if(($normalised != 'mailbox') && (x($a->contacts[$normalised]))) @@ -386,20 +564,23 @@ function conversation(&$a, $items, $mode, $update, $preview = false) { $drop = array( 'dropping' => $dropping, - 'select' => t('Select'), + 'pagedrop' => $page_dropping, + 'select' => t('Select'), 'delete' => t('Delete'), ); $star = false; $isstarred = "unstarred"; - + $lock = false; $likebuttons = false; $shareable = false; $body = prepare_body($item,true); - - //$tmp_item = replace_macros($tpl,array( + + + list($categories, $folders) = get_cats_and_terms($item); + $tmp_item = array( 'template' => $tpl, 'id' => (($preview) ? 'P0' : $item['item_id']), @@ -412,7 +593,17 @@ function conversation(&$a, $items, $mode, $update, $preview = false) { 'thumb' => $profile_avatar, 'title' => template_escape($item['title']), 'body' => template_escape($body), + 'tags' => template_escape($tags), + 'hashtags' => template_escape($hashtags), + 'mentions' => template_escape($mentions), + 'txt_cats' => t('Categories:'), + 'txt_folders' => t('Filed under:'), + 'has_cats' => ((count($categories)) ? 'true' : ''), + 'has_folders' => ((count($folders)) ? 'true' : ''), + 'categories' => $categories, + 'folders' => $folders, 'text' => strip_tags(template_escape($body)), + 'localtime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'r'), 'ago' => (($item['app']) ? sprintf( t('%s from %s'),relative_date($item['created']),$item['app']) : relative_date($item['created'])), 'location' => template_escape($location), 'indent' => '', @@ -431,6 +622,7 @@ function conversation(&$a, $items, $mode, $update, $preview = false) { 'conv' => (($preview) ? '' : array('href'=> $a->get_baseurl($ssl_state) . '/display/' . $nickname . '/' . $item['id'], 'title'=> t('View in context'))), 'previewing' => $previewing, 'wait' => t('Please wait'), + 'thread_level' => 1, ); $arr = array('item' => $item, 'output' => $tmp_item); @@ -445,371 +637,56 @@ function conversation(&$a, $items, $mode, $update, $preview = false) { else { // Normal View + $page_template = get_markup_template("threaded_conversation.tpl"); + require_once('object/Conversation.php'); + require_once('object/Item.php'); - // Figure out how many comments each parent has - // (Comments all have gravity of 6) - // Store the result in the $comments array + $conv = new Conversation($mode, $preview); - $comments = array(); + // get all the topmost parents + // this shouldn't be needed, as we should have only them in our array + // But for now, this array respects the old style, just in case + + $threads = array(); foreach($items as $item) { - if((intval($item['gravity']) == 6) && ($item['id'] != $item['parent'])) { - if(! x($comments,$item['parent'])) - $comments[$item['parent']] = 1; - else - $comments[$item['parent']] += 1; - } elseif(! x($comments,$item['parent'])) - $comments[$item['parent']] = 0; // avoid notices later on - } - // map all the like/dislike activities for each parent item - // Store these in the $alike and $dlike arrays - - foreach($items as $item) { + // Can we put this after the visibility check? like_puller($a,$item,$alike,'like'); like_puller($a,$item,$dlike,'dislike'); + + // Only add what is visible + if($item['network'] === NETWORK_MAIL && local_user() != $item['uid']) { + continue; + } + if(! visible_activity($item)) { + continue; + } + + $item['pagedrop'] = $page_dropping; + + if($item['id'] == $item['parent']) { + $item_object = new Item($item); + $conv->add_thread($item_object); + } } - $comments_collapsed = false; - $comments_seen = 0; - $comment_lastcollapsed = false; - $comment_firstcollapsed = false; - $blowhard = 0; - $blowhard_count = 0; - - - foreach($items as $item) { - - $comment = ''; - $template = $tpl; - $commentww = ''; - $sparkle = ''; - $owner_url = $owner_photo = $owner_name = ''; - - // We've already parsed out like/dislike for special treatment. We can ignore them now - - if(((activity_match($item['verb'],ACTIVITY_LIKE)) - || (activity_match($item['verb'],ACTIVITY_DISLIKE))) - && ($item['id'] != $item['parent'])) - continue; - - $toplevelpost = (($item['id'] == $item['parent']) ? true : false); - - - // Take care of author collapsing and comment collapsing - // (author collapsing is currently disabled) - // If a single author has more than 3 consecutive top-level posts, squash the remaining ones. - // If there are more than two comments, squash all but the last 2. - - if($toplevelpost) { - - $item_writeable = (($item['writable'] || $item['self']) ? true : false); - - $comments_seen = 0; - $comments_collapsed = false; - $comment_lastcollapsed = false; - $comment_firstcollapsed = false; - - $threadsid++; - $threads[$threadsid]['id'] = $item['item_id']; - $threads[$threadsid]['private'] = $item['private']; - $threads[$threadsid]['items'] = array(); - - } - else { - - // prevent private email reply to public conversation from leaking. - if($item['network'] === NETWORK_MAIL && local_user() != $item['uid']) - continue; - - $comments_seen ++; - $comment_lastcollapsed = false; - $comment_firstcollapsed = false; - } - - $override_comment_box = ((($page_writeable) && ($item_writeable)) ? true : false); - $show_comment_box = ((($page_writeable) && ($item_writeable) && ($comments_seen == $comments[$item['parent']])) ? true : false); - - - if(($comments[$item['parent']] > 2) && ($comments_seen <= ($comments[$item['parent']] - 2)) && ($item['gravity'] == 6)) { - - if (!$comments_collapsed){ - $threads[$threadsid]['num_comments'] = sprintf( tt('%d comment','%d comments',$comments[$item['parent']]),$comments[$item['parent']] ); - $threads[$threadsid]['hide_text'] = t('show more'); - $comments_collapsed = true; - $comment_firstcollapsed = true; - } - } - if(($comments[$item['parent']] > 2) && ($comments_seen == ($comments[$item['parent']] - 1))) { - - $comment_lastcollapsed = true; - } - - $redirect_url = $a->get_baseurl($ssl_state) . '/redir/' . $item['cid'] ; - - $lock = ((($item['private'] == 1) || (($item['uid'] == local_user()) && (strlen($item['allow_cid']) || strlen($item['allow_gid']) - || strlen($item['deny_cid']) || strlen($item['deny_gid'])))) - ? t('Private Message') - : false); - - - // Top-level wall post not written by the wall owner (wall-to-wall) - // First figure out who owns it. - - $osparkle = ''; - - if(($toplevelpost) && (! $item['self']) && ($mode !== 'profile')) { - - if($item['wall']) { - - // On the network page, I am the owner. On the display page it will be the profile owner. - // This will have been stored in $a->page_contact by our calling page. - // Put this person as the wall owner of the wall-to-wall notice. - - $owner_url = zrl($a->page_contact['url']); - $owner_photo = $a->page_contact['thumb']; - $owner_name = $a->page_contact['name']; - $template = $wallwall; - $commentww = 'ww'; - } - - if((! $item['wall']) && $item['owner-link']) { - - $owner_linkmatch = (($item['owner-link']) && link_compare($item['owner-link'],$item['author-link'])); - $alias_linkmatch = (($item['alias']) && link_compare($item['alias'],$item['author-link'])); - $owner_namematch = (($item['owner-name']) && $item['owner-name'] == $item['author-name']); - if((! $owner_linkmatch) && (! $alias_linkmatch) && (! $owner_namematch)) { - - // The author url doesn't match the owner (typically the contact) - // and also doesn't match the contact alias. - // The name match is a hack to catch several weird cases where URLs are - // all over the park. It can be tricked, but this prevents you from - // seeing "Bob Smith to Bob Smith via Wall-to-wall" and you know darn - // well that it's the same Bob Smith. - - // But it could be somebody else with the same name. It just isn't highly likely. - - - $owner_url = $item['owner-link']; - $owner_photo = $item['owner-avatar']; - $owner_name = $item['owner-name']; - $template = $wallwall; - $commentww = 'ww'; - // If it is our contact, use a friendly redirect link - if((link_compare($item['owner-link'],$item['url'])) - && ($item['network'] === NETWORK_DFRN)) { - $owner_url = $redirect_url; - $osparkle = ' sparkle'; - } - else - $owner_url = zrl($owner_url); - } - } - } - - $likebuttons = ''; - $shareable = ((($profile_owner == local_user()) && ($item['private'] != 1)) ? true : false); - - if($page_writeable) { -/* if($toplevelpost) { */ - $likebuttons = array( - 'like' => array( t("I like this \x28toggle\x29"), t("like")), - 'dislike' => array( t("I don't like this \x28toggle\x29"), t("dislike")), - ); - if ($shareable) $likebuttons['share'] = array( t('Share this'), t('share')); -/* } */ - - $qc = $qcomment = null; - - if(in_array('qcomment',$a->plugins)) { - $qc = ((local_user()) ? get_pconfig(local_user(),'qcomment','words') : null); - $qcomment = (($qc) ? explode("\n",$qc) : null); - } - - if(($show_comment_box) || (($show_comment_box == false) && ($override_comment_box == false) && ($item['last-child']))) { - $comment = replace_macros($cmnt_tpl,array( - '$return_path' => '', - '$jsreload' => (($mode === 'display') ? $_SESSION['return_url'] : ''), - '$type' => (($mode === 'profile') ? 'wall-comment' : 'net-comment'), - '$id' => $item['item_id'], - '$parent' => $item['parent'], - '$qcomment' => $qcomment, - '$profile_uid' => $profile_owner, - '$mylink' => $a->contact['url'], - '$mytitle' => t('This is you'), - '$myphoto' => $a->contact['thumb'], - '$comment' => t('Comment'), - '$submit' => t('Submit'), - '$edbold' => t('Bold'), - '$editalic' => t('Italic'), - '$eduline' => t('Underline'), - '$edquote' => t('Quote'), - '$edcode' => t('Code'), - '$edimg' => t('Image'), - '$edurl' => t('Link'), - '$edvideo' => t('Video'), - '$preview' => t('Preview'), - '$ww' => (($mode === 'network') ? $commentww : '') - )); - } - } - - if(local_user() && link_compare($a->contact['url'],$item['author-link'])) - $edpost = array($a->get_baseurl($ssl_state)."/editpost/".$item['id'], t("Edit")); - else - $edpost = false; - - $drop = ''; - $dropping = false; - - if((intval($item['contact-id']) && $item['contact-id'] == remote_user()) || ($item['uid'] == local_user())) - $dropping = true; - - $drop = array( - 'dropping' => $dropping, - 'select' => t('Select'), - 'delete' => t('Delete'), - ); - - $star = false; - $filer = false; - - $isstarred = "unstarred"; - if ($profile_owner == local_user()) { - if($toplevelpost) { - $isstarred = (($item['starred']) ? "starred" : "unstarred"); - - $star = array( - 'do' => t("add star"), - 'undo' => t("remove star"), - 'toggle' => t("toggle star status"), - 'classdo' => (($item['starred']) ? "hidden" : ""), - 'classundo' => (($item['starred']) ? "" : "hidden"), - 'starred' => t('starred'), - 'tagger' => t("add tag"), - 'classtagger' => "", - ); - } - $filer = t("save to folder"); - } - - - $photo = $item['photo']; - $thumb = $item['thumb']; - - // Post was remotely authored. - - $diff_author = ((link_compare($item['url'],$item['author-link'])) ? false : true); - - $profile_name = (((strlen($item['author-name'])) && $diff_author) ? $item['author-name'] : $item['name']); - - if($item['author-link'] && (! $item['author-name'])) - $profile_name = $item['author-link']; - - $sp = false; - $profile_link = best_link_url($item,$sp); - if($profile_link === 'mailbox') - $profile_link = ''; - if($sp) - $sparkle = ' sparkle'; - else - $profile_link = zrl($profile_link); - - $normalised = normalise_link((strlen($item['author-link'])) ? $item['author-link'] : $item['url']); - if(($normalised != 'mailbox') && (x($a->contacts,$normalised))) - $profile_avatar = $a->contacts[$normalised]['thumb']; - else - $profile_avatar = (((strlen($item['author-avatar'])) && $diff_author) ? $item['author-avatar'] : $a->get_cached_avatar_image($thumb)); - - $like = ((x($alike,$item['uri'])) ? format_like($alike[$item['uri']],$alike[$item['uri'] . '-l'],'like',$item['uri']) : ''); - $dislike = ((x($dlike,$item['uri'])) ? format_like($dlike[$item['uri']],$dlike[$item['uri'] . '-l'],'dislike',$item['uri']) : ''); - - $locate = array('location' => $item['location'], 'coord' => $item['coord'], 'html' => ''); - call_hooks('render_location',$locate); - - $location = ((strlen($locate['html'])) ? $locate['html'] : render_location_google($locate)); - - $indent = (($toplevelpost) ? '' : ' comment'); - - if(strcmp(datetime_convert('UTC','UTC',$item['created']),datetime_convert('UTC','UTC','now - 12 hours')) > 0) - $indent .= ' shiny'; - - // - localize_item($item); - - - $tags=array(); - foreach(explode(',',$item['tag']) as $tag){ - $tag = trim($tag); - if ($tag!="") $tags[] = bbcode($tag); - } - - // Build the HTML - - $body = prepare_body($item,true); - //$tmp_item = replace_macros($template, - $tmp_item = array( - // collapse comments in template. I don't like this much... - 'comment_firstcollapsed' => $comment_firstcollapsed, - 'comment_lastcollapsed' => $comment_lastcollapsed, - // template to use to render item (wall, walltowall, search) - 'template' => $template, - - 'type' => implode("",array_slice(explode("/",$item['verb']),-1)), - 'tags' => $tags, - 'body' => template_escape($body), - 'text' => strip_tags(template_escape($body)), - 'id' => $item['item_id'], - 'linktitle' => sprintf( t('View %s\'s profile @ %s'), $profile_name, ((strlen($item['author-link'])) ? $item['author-link'] : $item['url'])), - 'olinktitle' => sprintf( t('View %s\'s profile @ %s'), $profile_name, ((strlen($item['owner-link'])) ? $item['owner-link'] : $item['url'])), - 'to' => t('to'), - 'wall' => t('Wall-to-Wall'), - 'vwall' => t('via Wall-To-Wall:'), - 'profile_url' => $profile_link, - 'item_photo_menu' => item_photo_menu($item), - 'name' => template_escape($profile_name), - 'thumb' => $profile_avatar, - 'osparkle' => $osparkle, - 'sparkle' => $sparkle, - 'title' => template_escape($item['title']), - 'ago' => (($item['app']) ? sprintf( t('%s from %s'),relative_date($item['created']),$item['app']) : relative_date($item['created'])), - 'lock' => $lock, - 'location' => template_escape($location), - 'indent' => $indent, - 'owner_url' => $owner_url, - 'owner_photo' => $owner_photo, - 'owner_name' => template_escape($owner_name), - 'plink' => get_plink($item), - 'edpost' => $edpost, - 'isstarred' => $isstarred, - 'star' => $star, - 'filer' => $filer, - 'drop' => $drop, - 'vote' => $likebuttons, - 'like' => $like, - 'dislike' => $dislike, - 'comment' => $comment, - 'previewing' => $previewing, - 'wait' => t('Please wait'), - - ); - - - $arr = array('item' => $item, 'output' => $tmp_item); - call_hooks('display_item', $arr); - - $threads[$threadsid]['items'][] = $arr['output']; + $threads = $conv->get_template_data($alike, $dlike); + if(!$threads) { + logger('[ERROR] conversation : Failed to get template data.', LOGGER_DEBUG); + $threads = array(); } } } - $page_template = get_markup_template("conversation.tpl"); $o = replace_macros($page_template, array( '$baseurl' => $a->get_baseurl($ssl_state), + '$live_update' => $live_update_div, + '$remove' => t('remove'), '$mode' => $mode, '$user' => $a->user, '$threads' => $threads, - '$dropping' => ($dropping?t('Delete Selected Items'):False), + '$dropping' => ($page_dropping?t('Delete Selected Items'):False), )); return $o; @@ -856,14 +733,20 @@ function item_photo_menu($item){ if(! count($a->contacts)) load_contact_links(local_user()); } + $sub_link=""; + $poke_link=""; $contact_url=""; $pm_url=""; $status_link=""; $photos_link=""; $posts_link=""; + if((local_user()) && local_user() == $item['uid'] && $item['parent'] == $item['id'] && (! $item['self'])) { + $sub_link = 'javascript:dosubthread(' . $item['id'] . '); return false;'; + } + $sparkle = false; - $profile_link = best_link_url($item,$sparkle,$ssl_state); + $profile_link = best_link_url($item,$sparkle,$ssl_state); if($profile_link === 'mailbox') $profile_link = ''; @@ -879,12 +762,13 @@ function item_photo_menu($item){ $profile_link = zrl($profile_link); if(local_user() && local_user() == $item['uid'] && link_compare($item['url'],$item['author-link'])) { $cid = $item['contact-id']; - } + } else { $cid = 0; } } if(($cid) && (! $item['self'])) { + $poke_link = $a->get_baseurl($ssl_state) . '/poke/?f=&c=' . $cid; $contact_url = $a->get_baseurl($ssl_state) . '/contacts/' . $cid; $posts_link = $a->get_baseurl($ssl_state) . '/network/?cid=' . $cid; @@ -901,24 +785,30 @@ function item_photo_menu($item){ } $menu = Array( + t("Follow Thread") => $sub_link, t("View Status") => $status_link, t("View Profile") => $profile_link, t("View Photos") => $photos_link, - t("Network Posts") => $posts_link, + t("Network Posts") => $posts_link, t("Edit Contact") => $contact_url, t("Send PM") => $pm_url, + t("Poke") => $poke_link ); - - + + $args = array('item' => $item, 'menu' => $menu); - + call_hooks('item_photo_menu', $args); - $menu = $args['menu']; + $menu = $args['menu']; $o = ""; foreach($menu as $k=>$v){ - if ($v!="") $o .= "
  • $k
  • \n"; + if(strpos($v,'javascript:') === 0) { + $v = substr($v,11); + $o .= "
  • $k
  • \n"; + } + elseif ($v!="") $o .= "
  • $k
  • \n"; } return $o; }} @@ -946,7 +836,7 @@ function like_puller($a,$item,&$arr,$mode) { $arr[$item['thr-parent'] . '-l'] = array(); if(! isset($arr[$item['thr-parent']])) $arr[$item['thr-parent']] = 1; - else + else $arr[$item['thr-parent']] ++; $arr[$item['thr-parent'] . '-l'][] = '' . $item['author-name'] . ''; } @@ -967,10 +857,10 @@ function format_like($cnt,$arr,$type,$id) { $o .= (($type === 'like') ? sprintf( t('%s likes this.'), $arr[0]) : sprintf( t('%s doesn\'t like this.'), $arr[0])) . EOL ; else { $spanatts = 'class="fakelink" onclick="openClose(\'' . $type . 'list-' . $id . '\');"'; - $o .= (($type === 'like') ? + $o .= (($type === 'like') ? sprintf( t('%2$d people like this.'), $spanatts, $cnt) - : - sprintf( t('%2$d people don\'t like this.'), $spanatts, $cnt) ); + : + sprintf( t('%2$d people don\'t like this.'), $spanatts, $cnt) ); $o .= EOL ; $total = count($arr); if($total >= MAX_LIKERS) @@ -990,7 +880,7 @@ function format_like($cnt,$arr,$type,$id) { function status_editor($a,$x, $notes_cid = 0, $popup=false) { $o = ''; - + $geotag = (($x['allow_location']) ? get_markup_template('jot_geotag.tpl') : ''); $plaintext = false; @@ -998,8 +888,25 @@ function status_editor($a,$x, $notes_cid = 0, $popup=false) { $plaintext = true; $tpl = get_markup_template('jot-header.tpl'); - $a->page['htmlhead'] .= replace_macros($tpl, array( + '$newpost' => 'true', + '$baseurl' => $a->get_baseurl(true), + '$editselect' => (($plaintext) ? 'none' : '/(profile-jot-text|prvmail-text)/'), + '$geotag' => $geotag, + '$nickname' => $x['nickname'], + '$ispublic' => t('Visible to everybody'), + '$linkurl' => t('Please enter a link URL:'), + '$vidurl' => t("Please enter a video link/URL:"), + '$audurl' => t("Please enter an audio link/URL:"), + '$term' => t('Tag term:'), + '$fileas' => t('Save to Folder:'), + '$whereareu' => t('Where are you right now?'), + '$delitems' => t('Delete item(s)?') + )); + + + $tpl = get_markup_template('jot-end.tpl'); + $a->page['end'] .= replace_macros($tpl, array( '$newpost' => 'true', '$baseurl' => $a->get_baseurl(true), '$editselect' => (($plaintext) ? 'none' : '/(profile-jot-text|prvmail-text)/'), @@ -1016,7 +923,7 @@ function status_editor($a,$x, $notes_cid = 0, $popup=false) { $tpl = get_markup_template("jot.tpl"); - + $jotplugins = ''; $jotnets = ''; @@ -1047,7 +954,7 @@ function status_editor($a,$x, $notes_cid = 0, $popup=false) { if($notes_cid) $jotnets .= ''; - $tpl = replace_macros($tpl,array('$jotplugins' => $jotplugins)); + $tpl = replace_macros($tpl,array('$jotplugins' => $jotplugins)); $o .= replace_macros($tpl,array( '$return_path' => $a->query_string, @@ -1090,24 +997,71 @@ function status_editor($a,$x, $notes_cid = 0, $popup=false) { '$bang' => $x['bang'], '$profile_uid' => $x['profile_uid'], '$preview' => t('Preview'), + '$sourceapp' => t($a->sourcename), + '$cancel' => t('Cancel'), + '$rand_num' => random_digits(12) )); if ($popup==true){ $o = ''; - + } return $o; } +function get_item_children($arr, $parent) { + $children = array(); + $a = get_app(); + foreach($arr as $item) { + if($item['id'] != $item['parent']) { + if(get_config('system','thread_allow') && $a->theme_thread_allow) { + // Fallback to parent-uri if thr-parent is not set + $thr_parent = $item['thr-parent']; + if($thr_parent == '') + $thr_parent = $item['parent-uri']; + + if($thr_parent == $parent['uri']) { + $item['children'] = get_item_children($arr, $item); + $children[] = $item; + } + } + else if($item['parent'] == $parent['id']) { + $children[] = $item; + } + } + } + return $children; +} + +function sort_item_children($items) { + $result = $items; + usort($result,'sort_thr_created_rev'); + foreach($result as $k => $i) { + if(count($result[$k]['children'])) { + $result[$k]['children'] = sort_item_children($result[$k]['children']); + } + } + return $result; +} + +function add_children_to_list($children, &$arr) { + foreach($children as $y) { + $arr[] = $y; + if(count($y['children'])) + add_children_to_list($y['children'], $arr); + } +} + function conv_sort($arr,$order) { if((!(is_array($arr) && count($arr)))) return array(); $parents = array(); + $children = array(); foreach($arr as $x) if($x['id'] == $x['parent']) @@ -1119,24 +1073,25 @@ function conv_sort($arr,$order) { usort($parents,'sort_thr_commented'); if(count($parents)) - foreach($parents as $i=>$_x) - $parents[$i]['children'] = array(); + foreach($parents as $i=>$_x) + $parents[$i]['children'] = get_item_children($arr, $_x); - foreach($arr as $x) { + /*foreach($arr as $x) { if($x['id'] != $x['parent']) { $p = find_thread_parent_index($parents,$x); if($p !== false) $parents[$p]['children'][] = $x; } - } + }*/ if(count($parents)) { foreach($parents as $k => $v) { if(count($parents[$k]['children'])) { - $y = $parents[$k]['children']; + $parents[$k]['children'] = sort_item_children($parents[$k]['children']); + /*$y = $parents[$k]['children']; usort($y,'sort_thr_created_rev'); - $parents[$k]['children'] = $y; + $parents[$k]['children'] = $y;*/ } - } + } } $ret = array(); @@ -1144,8 +1099,9 @@ function conv_sort($arr,$order) { foreach($parents as $x) { $ret[] = $x; if(count($x['children'])) - foreach($x['children'] as $y) - $ret[] = $y; + add_children_to_list($x['children'], $ret); + /*foreach($x['children'] as $y) + $ret[] = $y;*/ } } diff --git a/include/datetime.php b/include/datetime.php index 75ae685f3a..efcfa0a177 100644 --- a/include/datetime.php +++ b/include/datetime.php @@ -100,11 +100,33 @@ function datetime_convert($from = 'UTC', $to = 'UTC', $s = 'now', $fmt = "Y-m-d return str_replace('1','0',$d->format($fmt)); } - $d = new DateTime($s, new DateTimeZone($from)); - $d->setTimeZone(new DateTimeZone($to)); + try { + $from_obj = new DateTimeZone($from); + } + catch(Exception $e) { + $from_obj = new DateTimeZone('UTC'); + } + + try { + $d = new DateTime($s, $from_obj); + } + catch(Exception $e) { + logger('datetime_convert: exception: ' . $e->getMessage()); + $d = new DateTime('now', $from_obj); + } + + try { + $to_obj = new DateTimeZone($to); + } + catch(Exception $e) { + $to_obj = new DateTimeZone('UTC'); + } + + $d->setTimeZone($to_obj); return($d->format($fmt)); }} + // wrapper for date selector, tailored for use in birthday fields function dob($dob) { diff --git a/include/dba.php b/include/dba.php index 71c1ba8591..8d224b570f 100644 --- a/include/dba.php +++ b/include/dba.php @@ -71,22 +71,32 @@ class dba { } public function q($sql) { + global $a; if((! $this->db) || (! $this->connected)) return false; $this->error = ''; - //if (get_config("system", "db_log") != "") - // @file_put_contents(get_config("system", "db_log"), datetime_convert().':'.session_id(). ' Start '.$sql."\n", FILE_APPEND); + if(x($a->config,'system') && x($a->config['system'],'db_log')) + $stamp1 = microtime(true); if($this->mysqli) $result = @$this->db->query($sql); else $result = @mysql_query($sql,$this->db); - //if (get_config("system", "db_log") != "") - // @file_put_contents(get_config("system", "db_log"), datetime_convert().':'.session_id(). ' Stop '."\n", FILE_APPEND); + if(x($a->config,'system') && x($a->config['system'],'db_log')) { + $stamp2 = microtime(true); + $duration = round($stamp2-$stamp1, 3); + if ($duration > $a->config["system"]["db_loglimit"]) { + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + @file_put_contents($a->config["system"]["db_log"], $duration."\t". + basename($backtrace[1]["file"])."\t". + $backtrace[1]["line"]."\t".$backtrace[2]["function"]."\t". + substr($sql, 0, 2000)."\n", FILE_APPEND); + } + } if($this->mysqli) { if($this->db->errno) diff --git a/include/delivery.php b/include/delivery.php index 1328771a67..14226e4fba 100644 --- a/include/delivery.php +++ b/include/delivery.php @@ -328,8 +328,9 @@ function delivery_run($argv, $argc){ dbesc($nickname) ); - if(count($x)) { - if($owner['page-flags'] == PAGE_COMMUNITY && ! $x[0]['writable']) { + if($x && count($x)) { + $write_flag = ((($x[0]['rel']) && ($x[0]['rel'] != CONTACT_IS_SHARING)) ? true : false); + if((($owner['page-flags'] == PAGE_COMMUNITY) || ($write_flag)) && (! $x[0]['writable'])) { q("update contact set writable = 1 where id = %d limit 1", intval($x[0]['id']) ); diff --git a/include/diaspora.php b/include/diaspora.php index df388737a3..c5b724509c 100755 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -102,6 +102,37 @@ function diaspora_dispatch($importer,$msg) { return $ret; } +function diaspora_handle_from_contact($contact_id) { + $handle = False; + + logger("diaspora_handle_from_contact: contact id is " . $contact_id, LOGGER_DEBUG); + + $r = q("SELECT network, addr, self, url, nick FROM contact WHERE id = %d", + intval($contact_id) + ); + if($r) { + $contact = $r[0]; + + logger("diaspora_handle_from_contact: contact 'self' = " . $contact['self'] . " 'url' = " . $contact['url'], LOGGER_DEBUG); + + if($contact['network'] === NETWORK_DIASPORA) { + $handle = $contact['addr']; + +// logger("diaspora_handle_from_contact: contact id is a Diaspora person, handle = " . $handle, LOGGER_DEBUG); + } + elseif(($contact['network'] === NETWORK_DFRN) || ($contact['self'] == 1)) { + $baseurl_start = strpos($contact['url'],'://') + 3; + $baseurl_length = strpos($contact['url'],'/profile') - $baseurl_start; // allows installations in a subdirectory--not sure how Diaspora will handle + $baseurl = substr($contact['url'], $baseurl_start, $baseurl_length); + $handle = $contact['nick'] . '@' . $baseurl; + +// logger("diaspora_handle_from_contact: contact id is a DFRN person, handle = " . $handle, LOGGER_DEBUG); + } + } + + return $handle; +} + function diaspora_get_contact_by_handle($uid,$handle) { $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `addr` = '%s' LIMIT 1", dbesc(NETWORK_DIASPORA), @@ -110,6 +141,17 @@ function diaspora_get_contact_by_handle($uid,$handle) { ); if($r && count($r)) return $r[0]; + + $handle_parts = explode("@", $handle); + $nurl_sql = '%%://' . $handle_parts[1] . '%%/profile/' . $handle_parts[0]; + $r = q("SELECT * FROM contact WHERE network = '%s' AND uid = %d AND nurl LIKE '%s' LIMIT 1", + dbesc(NETWORK_DFRN), + intval($uid), + dbesc($nurl_sql) + ); + if($r && count($r)) + return $r[0]; + return false; } @@ -1236,6 +1278,7 @@ function diaspora_comment($importer,$xml,$msg) { $datarray['uid'] = $importer['uid']; $datarray['contact-id'] = $contact['id']; + $datarray['type'] = 'remote-comment'; $datarray['wall'] = $parent_item['wall']; $datarray['gravity'] = GRAVITY_COMMENT; $datarray['guid'] = $guid; @@ -1272,7 +1315,7 @@ function diaspora_comment($importer,$xml,$msg) { if(($parent_item['origin']) && (! $parent_author_signature)) { q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", intval($message_id), - dbesc($author_signed_data), + dbesc($signed_data), dbesc(base64_encode($author_signature)), dbesc($diaspora_handle) ); @@ -1281,7 +1324,7 @@ function diaspora_comment($importer,$xml,$msg) { // the existence of parent_author_signature means the parent_author or owner // is already relaying. - proc_run('php','include/notifier.php','comment',$message_id); + proc_run('php','include/notifier.php','comment-import',$message_id); } $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0 ", @@ -1318,7 +1361,7 @@ function diaspora_comment($importer,$xml,$msg) { 'verb' => ACTIVITY_POST, 'otype' => 'item', 'parent' => $conv_parent, - + 'parent_uri' => $parent_uri )); // only send one notification @@ -1673,8 +1716,8 @@ function diaspora_like($importer,$xml,$msg) { // likes on comments not supported here and likes on photos not supported by Diaspora - if($target_type !== 'Post') - return; +// if($target_type !== 'Post') +// return; $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']); if(! $contact) { @@ -1855,7 +1898,7 @@ EOT; if(! $parent_author_signature) { q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", intval($message_id), - dbesc($author_signed_data), + dbesc($signed_data), dbesc(base64_encode($author_signature)), dbesc($diaspora_handle) ); @@ -1866,7 +1909,7 @@ EOT; // is already relaying. The parent_item['origin'] indicates the message was created on our system if(($parent_item['origin']) && (! $parent_author_signature)) - proc_run('php','include/notifier.php','comment',$message_id); + proc_run('php','include/notifier.php','comment-import',$message_id); return; } @@ -1917,7 +1960,7 @@ function diaspora_signed_retraction($importer,$xml,$msg) { $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle); if(! $contact) { - logger('diaspora_signed_retraction: no contact'); + logger('diaspora_signed_retraction: no contact ' . $diaspora_handle . ' for ' . $importer['uid']); return; } @@ -1992,7 +2035,7 @@ function diaspora_signed_retraction($importer,$xml,$msg) { // is already relaying. logger('diaspora_signed_retraction: relaying relayable_retraction'); - proc_run('php','include/notifier.php','relayable_retraction',$r[0]['id']); + proc_run('php','include/notifier.php','drop',$r[0]['id']); } } } @@ -2029,11 +2072,20 @@ function diaspora_profile($importer,$xml,$msg) { $image_url = unxmlify($xml->image_url); $birthday = unxmlify($xml->birthday); - $r = q("SELECT DISTINCT ( `resource-id` ) FROM `photo` WHERE `uid` = %d AND `contact-id` = %d AND `album` = 'Contact Photos' ", + + $handle_parts = explode("@", $diaspora_handle); + if($name === '') { + $name = $handle_parts[0]; + } + if(strpos($image_url, $handle_parts[1]) === false) { + $image_url = "http://" . $handle_parts[1] . $image_url; + } + +/* $r = q("SELECT DISTINCT ( `resource-id` ) FROM `photo` WHERE `uid` = %d AND `contact-id` = %d AND `album` = 'Contact Photos' ", intval($importer['uid']), intval($contact['id']) ); - $oldphotos = ((count($r)) ? $r : null); + $oldphotos = ((count($r)) ? $r : null);*/ require_once('include/Photo.php'); @@ -2066,7 +2118,7 @@ function diaspora_profile($importer,$xml,$msg) { intval($importer['uid']) ); - if($r) { +/* if($r) { if($oldphotos) { foreach($oldphotos as $ph) { q("DELETE FROM `photo` WHERE `uid` = %d AND `contact-id` = %d AND `album` = 'Contact Photos' AND `resource-id` = '%s' ", @@ -2076,7 +2128,7 @@ function diaspora_profile($importer,$xml,$msg) { ); } } - } + } */ return; @@ -2119,7 +2171,6 @@ function diaspora_unshare($me,$contact) { } - function diaspora_send_status($item,$owner,$contact,$public_batch = false) { $a = get_app(); @@ -2153,8 +2204,6 @@ function diaspora_send_status($item,$owner,$contact,$public_batch = false) { } } */ - // Removal of tags - $body = preg_replace('/#\[url\=(\w+.*?)\](\w+.*?)\[\/url\]/i', '#$2', $body); //if(strlen($title)) // $body = "[b]".html_entity_decode($title)."[/b]\n\n".$body; @@ -2253,22 +2302,31 @@ function diaspora_send_followup($item,$owner,$contact,$public_batch = false) { $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); // $theiraddr = $contact['addr']; - // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always - // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. - // The only item with `parent` and `id` as the parent id is the parent item. - $p = q("select guid from item where parent = %d and id = %d limit 1", - intval($item['parent']), - intval($item['parent']) - ); + // Diaspora doesn't support threaded comments + /*if($item['thr-parent']) { + $p = q("select guid, type, uri, `parent-uri` from item where uri = '%s' limit 1", + dbesc($item['thr-parent']) + ); + } + else {*/ + // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always + // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. + // The only item with `parent` and `id` as the parent id is the parent item. + $p = q("select guid, type, uri, `parent-uri` from item where parent = %d and id = %d limit 1", + intval($item['parent']), + intval($item['parent']) + ); + //} if(count($p)) - $parent_guid = $p[0]['guid']; + $parent = $p[0]; else return; if($item['verb'] === ACTIVITY_LIKE) { $tpl = get_markup_template('diaspora_like.tpl'); $like = true; - $target_type = 'Post'; + $target_type = ( $parent['uri'] === $parent['parent-uri'] ? 'Post' : 'Comment'); +// $target_type = (strpos($parent['type'], 'comment') ? 'Comment' : 'Post'); // $positive = (($item['deleted']) ? 'false' : 'true'); $positive = 'true'; @@ -2285,15 +2343,15 @@ function diaspora_send_followup($item,$owner,$contact,$public_batch = false) { // sign it if($like) - $signed_text = $item['guid'] . ';' . $target_type . ';' . $parent_guid . ';' . $positive . ';' . $myaddr; + $signed_text = $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $positive . ';' . $myaddr; else - $signed_text = $item['guid'] . ';' . $parent_guid . ';' . $text . ';' . $myaddr; + $signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $myaddr; $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); $msg = replace_macros($tpl,array( '$guid' => xmlify($item['guid']), - '$parent_guid' => xmlify($parent_guid), + '$parent_guid' => xmlify($parent['guid']), '$target_type' =>xmlify($target_type), '$authorsig' => xmlify($authorsig), '$body' => xmlify($text), @@ -2320,16 +2378,23 @@ function diaspora_send_relay($item,$owner,$contact,$public_batch = false) { $body = $item['body']; $text = html_entity_decode(bb2diaspora($body)); - - // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always - // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. - // The only item with `parent` and `id` as the parent id is the parent item. - $p = q("select guid from item where parent = %d and id = %d limit 1", - intval($item['parent']), - intval($item['parent']) - ); + // Diaspora doesn't support threaded comments + /*if($item['thr-parent']) { + $p = q("select guid, type, uri, `parent-uri` from item where uri = '%s' limit 1", + dbesc($item['thr-parent']) + ); + } + else {*/ + // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always + // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. + // The only item with `parent` and `id` as the parent id is the parent item. + $p = q("select guid, type, uri, `parent-uri` from item where parent = %d and id = %d limit 1", + intval($item['parent']), + intval($item['parent']) + ); + //} if(count($p)) - $parent_guid = $p[0]['guid']; + $parent = $p[0]; else return; @@ -2347,7 +2412,7 @@ function diaspora_send_relay($item,$owner,$contact,$public_batch = false) { elseif($item['verb'] === ACTIVITY_LIKE) { $like = true; - $target_type = 'Post'; + $target_type = ( $parent['uri'] === $parent['parent-uri'] ? 'Post' : 'Comment'); // $positive = (($item['deleted']) ? 'false' : 'true'); $positive = 'true'; @@ -2361,7 +2426,7 @@ function diaspora_send_relay($item,$owner,$contact,$public_batch = false) { // fetch the original signature if the relayable was created by a Diaspora // or DFRN user. Relayables for other networks are not supported. - $r = q("select * from sign where " . $sql_sign_id . " = %d limit 1", +/* $r = q("select * from sign where " . $sql_sign_id . " = %d limit 1", intval($item['id']) ); if(count($r)) { @@ -2377,14 +2442,32 @@ function diaspora_send_relay($item,$owner,$contact,$public_batch = false) { // function is called logger('diaspora_send_relay: original author signature not found, cannot send relayable'); return; - } + }*/ + + /* Since the author signature is only checked by the parent, not by the relay recipients, + * I think it may not be necessary for us to do so much work to preserve all the original + * signatures. The important thing that Diaspora DOES need is the original creator's handle. + * Let's just generate that and forget about all the original author signature stuff. + * + * Note: this might be more of an problem if we want to support likes on comments for older + * versions of Diaspora (diaspora-pistos), but since there are a number of problems with + * doing that, let's ignore it for now. + * + * Currently, only DFRN contacts are supported. StatusNet shouldn't be hard, but it hasn't + * been done yet + */ + + $handle = diaspora_handle_from_contact($item['contact-id']); + if(! $handle) + return; + if($relay_retract) $sender_signed_text = $item['guid'] . ';' . $target_type; elseif($like) - $sender_signed_text = $item['guid'] . ';' . $target_type . ';' . $parent_guid . ';' . $positive . ';' . $handle; + $sender_signed_text = $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $positive . ';' . $handle; else - $sender_signed_text = $item['guid'] . ';' . $parent_guid . ';' . $text . ';' . $handle; + $sender_signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $handle; // Sign the relayable with the top-level owner's signature // @@ -2401,7 +2484,7 @@ function diaspora_send_relay($item,$owner,$contact,$public_batch = false) { $msg = replace_macros($tpl,array( '$guid' => xmlify($item['guid']), - '$parent_guid' => xmlify($parent_guid), + '$parent_guid' => xmlify($parent['guid']), '$target_type' =>xmlify($target_type), '$authorsig' => xmlify($authorsig), '$parentsig' => xmlify($parentauthorsig), @@ -2517,7 +2600,7 @@ function diaspora_send_mail($item,$owner,$contact) { } -function diaspora_transmit($owner,$contact,$slap,$public_batch) { +function diaspora_transmit($owner,$contact,$slap,$public_batch,$queue_run=false) { $enabled = intval(get_config('system','diaspora_enabled')); if(! $enabled) { @@ -2534,7 +2617,7 @@ function diaspora_transmit($owner,$contact,$slap,$public_batch) { logger('diaspora_transmit: ' . $logid . ' ' . $dest_url); - if(was_recently_delayed($contact['id'])) { + if( (! $queue_run) && (was_recently_delayed($contact['id'])) ) { $return_code = 0; } else { diff --git a/include/email.php b/include/email.php index b43ae0dc1c..46feb4582b 100644 --- a/include/email.php +++ b/include/email.php @@ -48,8 +48,8 @@ function construct_mailbox_name($mailacct) { function email_msg_meta($mbox,$uid) { - $ret = (($mbox && $uid) ? @imap_fetch_overview($mbox,$uid,FT_UID) : array(array())); - return ((count($ret)) ? $ret[0] : array()); + $ret = (($mbox && $uid) ? @imap_fetch_overview($mbox,$uid,FT_UID) : array(array())); // POSSIBLE CLEANUP --> array(array()) is probably redundant now + return ((count($ret)) ? $ret : array()); } function email_msg_headers($mbox,$uid) { diff --git a/include/enotify.php b/include/enotify.php index 5e073bf3ca..b4331f0926 100644 --- a/include/enotify.php +++ b/include/enotify.php @@ -1,5 +1,7 @@ ' . $sitename . ''); + $itemlink = $params['link']; + } + if($params['type'] == NOTIFY_TAGSHARE) { $subject = sprintf( t('[Friendica:Notify] %s tagged your post') , $params['source_name']); $preamble = sprintf( t('%1$s tagged your post at %2$s') , $params['source_name'], $sitename); @@ -306,7 +326,7 @@ function notification($params) { // If so, create the record of it and use a message-id smtp header. if(!$r) { - logger("norify_id:" . intval($notify_id). ", parent: " . intval($params['parent']) . "uid: " . + logger("notify_id:" . intval($notify_id). ", parent: " . intval($params['parent']) . "uid: " . intval($params['uid']), LOGGER_DEBUG); $r = q("insert into `notify-threads` (`notify-id`, `master-parent-item`, `receiver-uid`, `parent-item`) values(%d,%d,%d,%d)", @@ -477,6 +497,7 @@ class enotify { $multipartMessageBody, // message body $messageHeader // message headers ); + logger("notification: enotify::send header " . 'To: ' . $params['toEmail'] . "\n" . $messageHeader, LOGGER_DEBUG); logger("notification: enotify::send returns " . $res, LOGGER_DEBUG); } } diff --git a/include/group.php b/include/group.php index 854ac06a9a..7ede242c08 100644 --- a/include/group.php +++ b/include/group.php @@ -40,7 +40,7 @@ function group_add($uid,$name) { function group_rmv($uid,$name) { $ret = false; if(x($uid) && x($name)) { - $r = q("SELECT * FROM `group` WHERE `uid` = %d AND `name` = '%s' LIMIT 1", + $r = q("SELECT id FROM `group` WHERE `uid` = %d AND `name` = '%s' LIMIT 1", intval($uid), dbesc($name) ); @@ -49,6 +49,37 @@ function group_rmv($uid,$name) { if(! $group_id) return false; + // remove group from default posting lists + $r = q("SELECT def_gid, allow_gid, deny_gid FROM user WHERE uid = %d LIMIT 1", + intval($uid) + ); + if($r) { + $user_info = $r[0]; + $change = false; + + if($user_info['def_gid'] == $group_id) { + $user_info['def_gid'] = 0; + $change = true; + } + if(strpos($user_info['allow_gid'], '<' . $group_id . '>') !== false) { + $user_info['allow_gid'] = str_replace('<' . $group_id . '>', '', $user_info['allow_gid']); + $change = true; + } + if(strpos($user_info['deny_gid'], '<' . $group_id . '>') !== false) { + $user_info['deny_gid'] = str_replace('<' . $group_id . '>', '', $user_info['deny_gid']); + $change = true; + } + + if($change) { + q("UPDATE user SET def_gid = %d, allow_gid = '%s', deny_gid = '%s' WHERE uid = %d", + intval($user_info['def_gid']), + dbesc($user_info['allow_gid']), + dbesc($user_info['deny_gid']), + intval($uid) + ); + } + } + // remove all members $r = q("DELETE FROM `group_member` WHERE `uid` = %d AND `gid` = %d ", intval($uid), @@ -103,7 +134,7 @@ function group_add_member($uid,$name,$member,$gid = 0) { if((! $gid) || (! $uid) || (! $member)) return false; - $r = q("SELECT * FROM `group_member` WHERE `uid` = %d AND `id` = %d AND `contact-id` = %d LIMIT 1", + $r = q("SELECT * FROM `group_member` WHERE `uid` = %d AND `gid` = %d AND `contact-id` = %d LIMIT 1", intval($uid), intval($gid), intval($member) diff --git a/include/items.php b/include/items.php index e656fc2449..939cefc3dd 100755 --- a/include/items.php +++ b/include/items.php @@ -76,6 +76,7 @@ function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) killme(); $contact = $r[0]; + require_once('include/security.php'); $groups = init_groups_visitor($contact['id']); if(count($groups)) { @@ -374,6 +375,29 @@ function limit_body_size($body) { return $body; }} +function title_is_body($title, $body) { + + $title = strip_tags($title); + $title = trim($title); + $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title); + + $body = strip_tags($body); + $body = trim($body); + $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body); + + if (strlen($title) < strlen($body)) + $body = substr($body, 0, strlen($title)); + + if (($title != $body) and (substr($title, -3) == "...")) { + $pos = strrpos($title, "..."); + if ($pos > 0) { + $title = substr($title, 0, $pos); + $body = substr($body, 0, $pos); + } + } + + return($title == $body); +} @@ -400,6 +424,11 @@ function get_atom_elements($feed,$item) { $res['body'] = unxmlify($item->get_content()); $res['plink'] = unxmlify($item->get_link(0)); + // removing the content of the title if its identically to the body + // This helps with auto generated titles e.g. from tumblr + if (title_is_body($res["title"], $res["body"])) + $res['title'] = ""; + if($res['plink']) $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3)); else @@ -418,7 +447,7 @@ function get_atom_elements($feed,$item) { $res['author-avatar'] = unxmlify($link['attribs']['']['href']); } } - } + } $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor'); @@ -450,7 +479,7 @@ function get_atom_elements($feed,$item) { $res['author-avatar'] = unxmlify($link['attribs']['']['href']); } } - } + } $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject'); @@ -475,7 +504,7 @@ function get_atom_elements($feed,$item) { $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source'])); if($res['app'] === 'web') $res['app'] = 'OStatus'; - } + } // base64 encoded json structure representing Diaspora signature @@ -550,6 +579,7 @@ function get_atom_elements($feed,$item) { $res['body'] = escape_tags($res['body']); } + // this tag is obsolete but we keep it for really old sites $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow'); @@ -618,7 +648,7 @@ function get_atom_elements($feed,$item) { foreach($base as $link) { if(!x($res, 'owner-avatar') || !$res['owner-avatar']) { - if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar') + if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar') $res['owner-avatar'] = unxmlify($link['attribs']['']['href']); } } @@ -758,10 +788,41 @@ function get_atom_elements($feed,$item) { $res['target'] .= '' . "\n"; } + // This is some experimental stuff. By now retweets are shown with "RT:" + // But: There is data so that the message could be shown similar to native retweets + // There is some better way to parse this array - but it didn't worked for me. + $child = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["entry"][0]["child"]["http://activitystrea.ms/spec/1.0/"][object][0]["child"]; + if (is_array($child)) { + $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"]; + $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]; + $uri = $author["uri"][0]["data"]; + $name = $author["name"][0]["data"]; + $avatar = @array_shift($author["link"][2]["attribs"]); + $avatar = $avatar["href"]; + + if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) { + $res["owner-name"] = $res["author-name"]; + $res["owner-link"] = $res["author-link"]; + $res["owner-avatar"] = $res["author-avatar"]; + + $res["author-name"] = $name; + $res["author-link"] = $uri; + $res["author-avatar"] = $avatar; + + $res["body"] = html2bbcode($message); + } + } + $arr = array('feed' => $feed, 'item' => $item, 'result' => $res); call_hooks('parse_atom', $arr); + //if (($res["title"] != "") or (strpos($res["body"], "RT @") > 0)) { + //if (strpos($res["body"], "RT @") !== false) { + // $debugfile = tempnam("/home/ike/log", "item-res2-"); + // file_put_contents($debugfile, serialize($arr)); + //} + return $res; } @@ -817,6 +878,14 @@ function item_store($arr,$force_parent = false) { $arr['body'] = strip_tags($arr['body']); + if (version_compare(PHP_VERSION, '5.3.0', '>=')) { + require_once('Text/LanguageDetect.php'); + $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']); + $l = new Text_LanguageDetect; + $lng = $l->detectConfidence($naked_body); + $arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : ''); + } + $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0); $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string()); $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : ''); @@ -857,6 +926,8 @@ function item_store($arr,$force_parent = false) { $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 ); $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid()); + + $arr['thr-parent'] = $arr['parent-uri']; if($arr['parent-uri'] === $arr['uri']) { $parent_id = 0; $parent_deleted = 0; @@ -882,9 +953,8 @@ function item_store($arr,$force_parent = false) { // and re-attach to the conversation parent. if($r[0]['uri'] != $r[0]['parent-uri']) { - $arr['thr-parent'] = $arr['parent-uri']; $arr['parent-uri'] = $r[0]['parent-uri']; - $z = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d + $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1", dbesc($r[0]['parent-uri']), dbesc($r[0]['parent-uri']), @@ -924,7 +994,6 @@ function item_store($arr,$force_parent = false) { if($force_parent) { logger('item_store: $force_parent=true, reply converted to top-level post.'); $parent_id = 0; - $arr['thr-parent'] = $arr['parent-uri']; $arr['parent-uri'] = $arr['uri']; $arr['gravity'] = 0; } @@ -1116,6 +1185,15 @@ function tag_deliver($uid,$item_id) { // send a notification + // use a local photo if we have one + + $r = q("select * from contact where uid = %d and nurl = '%s' limit 1", + intval($u[0]['uid']), + dbesc(normalise_link($item['author-link'])) + ); + $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']); + + require_once('include/enotify.php'); notification(array( 'type' => NOTIFY_TAGSELF, @@ -1128,11 +1206,16 @@ function tag_deliver($uid,$item_id) { 'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item['id'], 'source_name' => $item['author-name'], 'source_link' => $item['author-link'], - 'source_photo' => $item['author-avatar'], + 'source_photo' => $photo, 'verb' => ACTIVITY_TAG, 'otype' => 'item' )); + + $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]); + + call_hooks('tagged', $arr); + if((! $community_page) && (! $prvgroup)) return; @@ -1179,6 +1262,59 @@ function tag_deliver($uid,$item_id) { +function tgroup_check($uid,$item) { + + $a = get_app(); + + $mention = false; + + // check that the message originated elsewhere and is a top-level post + + if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri'])) + return false; + + + $u = q("select * from user where uid = %d limit 1", + intval($uid) + ); + if(! count($u)) + return false; + + $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false); + $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false); + + + $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']); + + // Diaspora uses their own hardwired link URL in @-tags + // instead of the one we supply with webfinger + + $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']); + + $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) { + $mention = true; + logger('tgroup_check: mention found: ' . $mtch[2]); + } + } + } + + if(! $mention) + return false; + + if((! $community_page) && (! $prvgroup)) + return false; + + + + return true; + +} + + + @@ -1709,7 +1845,7 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) // Now process the feed - if($feed->get_item_quantity()) { + if($feed->get_item_quantity()) { logger('consume_feed: feed item count = ' . $feed->get_item_quantity()); @@ -1722,7 +1858,7 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) foreach($items as $item) { - $is_reply = false; + $is_reply = false; $item_id = $item->get_id(); $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to'); if(isset($rawthread[0]['attribs']['']['ref'])) { @@ -1735,12 +1871,17 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) if($pass == 1) continue; + // not allowed to post + + if($contact['rel'] == CONTACT_IS_FOLLOWER) + continue; + + // Have we seen it? If not, import it. - + $item_id = $item->get_id(); $datarray = get_atom_elements($feed,$item); - if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN)) $datarray['author-name'] = $contact['name']; if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN)) @@ -2010,6 +2151,14 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) $datarray['owner-avatar'] = $contact['thumb']; } + // We've allowed "followers" to reach this point so we can decide if they are + // posting an @-tag delivery, which followers are allowed to do for certain + // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it. + + if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray))) + continue; + + $r = item_store($datarray); continue; @@ -2041,6 +2190,121 @@ function local_delivery($importer,$data) { $feed->enable_order_by_date(false); $feed->init(); + + if($feed->error()) + logger('local_delivery: Error parsing XML: ' . $feed->error()); + + + // Check at the feed level for updated contact name and/or photo + + $name_updated = ''; + $new_name = ''; + $photo_timestamp = ''; + $photo_url = ''; + + + $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner'); + +// Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags +// if(! $rawtags) +// $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); + + if($rawtags) { + $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; + if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) { + $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']; + $new_name = $elems['name'][0]['data']; + } + if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) { + $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']); + $photo_url = $elems['link'][0]['attribs']['']['href']; + } + } + + if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) { + logger('local_delivery: Updating photo for ' . $importer['name']); + require_once("Photo.php"); + $photo_failure = false; + $have_photo = false; + + $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1", + intval($importer['id']), + intval($importer['importer_uid']) + ); + if(count($r)) { + $resource_id = $r[0]['resource-id']; + $have_photo = true; + } + else { + $resource_id = photo_new_resource(); + } + + $img_str = fetch_url($photo_url,true); + // guess mimetype from headers or filename + $type = guess_image_type($photo_url,true); + + + $img = new Photo($img_str, $type); + if($img->is_valid()) { + if($have_photo) { + q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d", + dbesc($resource_id), + intval($importer['id']), + intval($importer['importer_uid']) + ); + } + + $img->scaleImageSquare(175); + + $hash = $resource_id; + $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4); + + $img->scaleImage(80); + $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5); + + $img->scaleImage(48); + $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6); + + $a = get_app(); + + q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s' + WHERE `uid` = %d AND `id` = %d LIMIT 1", + dbesc(datetime_convert()), + dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()), + dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()), + dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()), + intval($importer['importer_uid']), + intval($importer['id']) + ); + } + } + + if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) { + $r = q("select * from contact where uid = %d and id = %d limit 1", + intval($importer['importer_uid']), + intval($importer['id']) + ); + + $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d LIMIT 1", + dbesc(notags(trim($new_name))), + dbesc(datetime_convert()), + intval($importer['importer_uid']), + intval($importer['id']) + ); + + // do our best to update the name on content items + + if(count($r)) { + q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d", + dbesc(notags(trim($new_name))), + dbesc($r[0]['name']), + dbesc($r[0]['url']), + intval($importer['importer_uid']) + ); + } + } + + /* // Currently unsupported - needs a lot of work $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' ); @@ -2280,6 +2544,7 @@ function local_delivery($importer,$data) { $is_a_remote_delete = false; + // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used? $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`, `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item` LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id` @@ -2293,7 +2558,7 @@ function local_delivery($importer,$data) { intval($importer['importer_uid']) ); if($r && count($r)) - $is_a_remote_delete = true; + $is_a_remote_delete = true; // Does this have the characteristics of a community or private group comment? // If it's a reply to a wall post on a community/prvgroup page it's a @@ -2437,22 +2702,32 @@ function local_delivery($importer,$data) { // Specifically, the recipient? $is_a_remote_comment = false; - - // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used? - $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`, - `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item` - LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s') - AND `item`.`uid` = %d - $sql_extra + $top_uri = $parent_uri; + + $r = q("select `item`.`parent-uri` from `item` + WHERE `item`.`uri` = '%s' LIMIT 1", - dbesc($parent_uri), - dbesc($parent_uri), - dbesc($parent_uri), - intval($importer['importer_uid']) + dbesc($parent_uri) ); - if($r && count($r)) - $is_a_remote_comment = true; + if($r && count($r)) { + $top_uri = $r[0]['parent-uri']; + + // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used? + $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`, + `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item` + LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id` + WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s') + AND `item`.`uid` = %d + $sql_extra + LIMIT 1", + dbesc($top_uri), + dbesc($top_uri), + dbesc($top_uri), + intval($importer['importer_uid']) + ); + if($r && count($r)) + $is_a_remote_comment = true; + } // Does this have the characteristics of a community or private group comment? // If it's a reply to a wall post on a community/prvgroup page it's a @@ -2506,15 +2781,6 @@ function local_delivery($importer,$data) { } - // TODO: make this next part work against both delivery threads of a community post - -// if((! link_compare($datarray['author-link'],$importer['url'])) && (! $community)) { -// logger('local_delivery: received relay claiming to be from ' . $importer['url'] . ' however comment author url is ' . $datarray['author-link'] ); - // they won't know what to do so don't report an error. Just quietly die. -// return 0; -// } - - // our user with $importer['importer_uid'] is the owner $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1", intval($importer['importer_uid']) @@ -2584,26 +2850,19 @@ function local_delivery($importer,$data) { } } -// if($community) { -// $newtag = '@[url=' . $a->get_baseurl() . '/profile/' . $importer['nickname'] . ']' . $importer['username'] . '[/url]'; -// if(! stristr($datarray['tag'],$newtag)) { -// if(strlen($datarray['tag'])) -// $datarray['tag'] .= ','; -// $datarray['tag'] .= $newtag; -// } -// } - $posted_id = item_store($datarray); $parent = 0; if($posted_id) { - $r = q("SELECT `parent` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", + $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", intval($posted_id), intval($importer['importer_uid']) ); - if(count($r)) + if(count($r)) { $parent = $r[0]['parent']; + $parent_uri = $r[0]['parent-uri']; + } if(! $is_like) { $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d", @@ -2620,7 +2879,7 @@ function local_delivery($importer,$data) { } if($posted_id && $parent) { - + proc_run('php',"include/notifier.php","comment-import","$posted_id"); if((! $is_like) && (! $importer['self'])) { @@ -2643,7 +2902,7 @@ function local_delivery($importer,$data) { 'verb' => ACTIVITY_POST, 'otype' => 'item', 'parent' => $parent, - + 'parent_uri' => $parent_uri, )); } @@ -2660,6 +2919,9 @@ function local_delivery($importer,$data) { $item_id = $item->get_id(); $datarray = get_atom_elements($feed,$item); + if($importer['rel'] == CONTACT_IS_FOLLOWER) + continue; + $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($item_id), intval($importer['importer_uid']) @@ -2754,7 +3016,7 @@ function local_delivery($importer,$data) { if(!x($datarray['type']) || $datarray['type'] != 'activity') { $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0", - dbesc($parent_uri), + dbesc($top_uri), intval($importer['importer_uid']) ); @@ -2792,6 +3054,7 @@ function local_delivery($importer,$data) { 'verb' => ACTIVITY_POST, 'otype' => 'item', 'parent' => $conv_parent, + 'parent_uri' => $parent_uri )); @@ -2881,7 +3144,8 @@ function local_delivery($importer,$data) { $datarray['uid'] = $importer['importer_uid']; $datarray['contact-id'] = $importer['id']; - if(! link_compare($datarray['owner-link'],$contact['url'])) { + + if(! link_compare($datarray['owner-link'],$importer['url'])) { // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery, // but otherwise there's a possible data mixup on the sender's system. // the tgroup delivery code called from item_store will correct it if it's a forum, @@ -2892,7 +3156,60 @@ function local_delivery($importer,$data) { $datarray['owner-avatar'] = $importer['thumb']; } - $r = item_store($datarray); + if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray))) + continue; + + $posted_id = item_store($datarray); + + if(stristr($datarray['verb'],ACTIVITY_POKE)) { + $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1)); + if(! $verb) + continue; + $xo = parse_xml_string($datarray['object'],false); + + if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) { + + // somebody was poked/prodded. Was it me? + + $links = parse_xml_string("".unxmlify($xo->link)."",false); + + foreach($links->link as $l) { + $atts = $l->attributes(); + switch($atts['rel']) { + case "alternate": + $Blink = $atts['href']; + break; + default: + break; + } + } + if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) { + + // send a notification + require_once('include/enotify.php'); + + notification(array( + 'type' => NOTIFY_POKE, + 'notify_flags' => $importer['notify-flags'], + 'language' => $importer['language'], + 'to_name' => $importer['username'], + 'to_email' => $importer['email'], + 'uid' => $importer['importer_uid'], + 'item' => $datarray, + 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id, + 'source_name' => stripslashes($datarray['author-name']), + 'source_link' => $datarray['author-link'], + 'source_photo' => ((link_compare($datarray['author-link'],$importer['url'])) + ? $importer['thumb'] : $datarray['author-avatar']), + 'verb' => $datarray['verb'], + 'otype' => 'person', + 'activity' => $verb, + + )); + } + } + } + continue; } } @@ -3100,7 +3417,6 @@ function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) { else $body = $item['body']; - $o = "\r\n\r\n\r\n"; if(is_array($author)) @@ -3110,7 +3426,7 @@ function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) { if(strlen($item['owner-name'])) $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']); - if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || ($item['thr-parent'])) { + if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) { $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']); $o .= '' . "\r\n"; } @@ -3469,10 +3785,23 @@ function drop_item($id,$interactive = true) { $owner = $item['uid']; + $cid = 0; + // check if logged in user is either the author or owner of this item - if((local_user() == $item['uid']) || (remote_user() == $item['contact-id'])) { + if(is_array($_SESSION['remote'])) { + foreach($_SESSION['remote'] as $visitor) { + if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) { + $cid = $visitor['cid']; + break; + } + } + } + + if((local_user() == $item['uid']) || ($cid) || (! $interactive)) { + + logger('delete item: ' . $item['id'], LOGGER_DEBUG); // delete the item $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d LIMIT 1", @@ -3572,10 +3901,10 @@ function drop_item($id,$interactive = true) { // send the notification upstream/downstream as the case may be + proc_run('php',"include/notifier.php","drop","$drop_id"); + if(! $interactive) return $owner; - - proc_run('php',"include/notifier.php","drop","$drop_id"); goaway($a->get_baseurl() . '/' . $_SESSION['return_url']); //NOTREACHED } @@ -3657,7 +3986,6 @@ function posted_date_widget($url,$uid,$wall) { return $o; } - function store_diaspora_retract_sig($item, $user, $baseurl) { // Note that we can't add a target_author_signature // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting @@ -3683,7 +4011,7 @@ function store_diaspora_retract_sig($item, $user, $baseurl) { } else { $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1", - $item['contact-id'] + $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id'] ); if(count($r)) { // The below handle only works for NETWORK_DFRN. I think that's ok, because this function diff --git a/include/lock.php b/include/lock.php index 5f1ca63231..707e33609e 100644 --- a/include/lock.php +++ b/include/lock.php @@ -73,5 +73,3 @@ function unlock_function($fn_name) { return; }} - -?> diff --git a/include/markdownify/markdownify.php b/include/markdownify/markdownify.php index 43730cb77f..0d4429a013 100644 --- a/include/markdownify/markdownify.php +++ b/include/markdownify/markdownify.php @@ -686,6 +686,10 @@ class Markdownify { # [1]: mailto:mail@example.com Title $tag['href'] = 'mailto:'.$bufferDecoded; } + + $this->out('['.$buffer.']('.$tag['href'].' "'.$tag['title'].'")', true); + +/* # [This link][id] foreach ($this->stack['a'] as $tag2) { if ($tag2['href'] == $tag['href'] && $tag2['title'] === $tag['title']) { @@ -699,6 +703,7 @@ class Markdownify { } $this->out('['.$buffer.']['.$tag['linkID'].']', true); +*/ } } /** @@ -735,6 +740,13 @@ class Markdownify { $this->parser->tagAttributes['src'] = $this->decode($this->parser->tagAttributes['src']); } +// ![Alt text](/path/to/img.jpg "Optional title") + if ($this->parser->tagAttributes['title'] != "") + $this->out('!['.$this->parser->tagAttributes['alt'].']('.$this->parser->tagAttributes['src'].' "'.$this->parser->tagAttributes['title'].'")', true); + else + $this->out('!['.$this->parser->tagAttributes['alt'].']('.$this->parser->tagAttributes['src'].')', true); + +/* # [This link][id] $link_id = false; if (!empty($this->stack['a'])) { @@ -759,6 +771,7 @@ class Markdownify { } $this->out('!['.$this->parser->tagAttributes['alt'].']['.$link_id.']', true); +*/ } /** * handle tags @@ -1181,4 +1194,4 @@ class Markdownify { function parent() { return end($this->parser->openTags); } -} \ No newline at end of file +} diff --git a/include/network.php b/include/network.php index 9e6f8355b7..0e1a63792f 100644 --- a/include/network.php +++ b/include/network.php @@ -14,15 +14,16 @@ function fetch_url($url,$binary = false, &$redirects = 0, $timeout = 0, $accept_ return false; @curl_setopt($ch, CURLOPT_HEADER, true); - + if (!is_null($accept_content)){ curl_setopt($ch,CURLOPT_HTTPHEADER, array ( "Accept: " . $accept_content )); } - + @curl_setopt($ch, CURLOPT_RETURNTRANSFER,true); - @curl_setopt($ch, CURLOPT_USERAGENT, "Friendica"); + //@curl_setopt($ch, CURLOPT_USERAGENT, "Friendica"); + @curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; Friendica)"); if(intval($timeout)) { @@ -59,7 +60,6 @@ function fetch_url($url,$binary = false, &$redirects = 0, $timeout = 0, $accept_ $base = $s; $curl_info = @curl_getinfo($ch); $http_code = $curl_info['http_code']; - // logger('fetch_url:' . $http_code . ' data: ' . $s); $header = ''; @@ -73,24 +73,22 @@ function fetch_url($url,$binary = false, &$redirects = 0, $timeout = 0, $accept_ } if($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307) { - $matches = array(); - preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches); - $newurl = trim(array_pop($matches)); + $matches = array(); + preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches); + $newurl = trim(array_pop($matches)); if(strpos($newurl,'/') === 0) $newurl = $url . $newurl; - $url_parsed = @parse_url($newurl); - if (isset($url_parsed)) { - $redirects++; - return fetch_url($newurl,$binary,$redirects,$timeout); - } - } + $url_parsed = @parse_url($newurl); + if (isset($url_parsed)) { + $redirects++; + return fetch_url($newurl,$binary,$redirects,$timeout); + } + } $a->set_curl_code($http_code); $body = substr($s,strlen($header)); - $a->set_curl_headers($header); - @curl_close($ch); return($body); }} @@ -800,8 +798,11 @@ function scale_external_images($s, $include_link = true, $scale_replace = false) $a = get_app(); + // Picture addresses can contain special characters + $s = htmlspecialchars_decode($s); + $matches = null; - $c = preg_match_all('/\[img\](.*?)\[\/img\]/ism',$s,$matches,PREG_SET_ORDER); + $c = preg_match_all('/\[img.*?\](.*?)\[\/img\]/ism',$s,$matches,PREG_SET_ORDER); if($c) { require_once('include/Photo.php'); foreach($matches as $mtch) { @@ -822,6 +823,12 @@ function scale_external_images($s, $include_link = true, $scale_replace = false) $scaled = $mtch[1]; $i = fetch_url($scaled); + $cache = get_config('system','itemcache'); + if (($cache != '') and is_dir($cache)) { + $cachefile = $cache."/".hash("md5", $scaled); + file_put_contents($cachefile, $i); + } + // guess mimetype from headers or filename $type = guess_image_type($mtch[1],true); @@ -847,6 +854,10 @@ function scale_external_images($s, $include_link = true, $scale_replace = false) } } } + + // replace the special char encoding + + $s = htmlspecialchars($s,ENT_QUOTES,'UTF-8'); return $s; } diff --git a/include/notifier.php b/include/notifier.php index f54efba31d..171b55fc37 100644 --- a/include/notifier.php +++ b/include/notifier.php @@ -18,6 +18,31 @@ require_once('include/html2plain.php'); * us by hosting providers. */ +/* + * The notifier is typically called with: + * + * proc_run('php', "include/notifier.php", COMMAND, ITEM_ID); + * + * where COMMAND is one of the following: + * + * activity (in diaspora.php, dfrn_confirm.php, profiles.php) + * comment-import (in diaspora.php, items.php) + * comment-new (in item.php) + * drop (in diaspora.php, items.php, photos.php) + * edit_post (in item.php) + * event (in events.php) + * expire (in items.php) + * like (in like.php, poke.php) + * mail (in message.php) + * suggest (in fsuggest.php) + * tag (in photos.php, poke.php, tagger.php) + * tgroup (in items.php) + * wall-new (in photos.php, item.php) + * + * and ITEM_ID is the id of the item in the database that needs to be sent to others. + */ + + function notifier_run($argv, $argc){ global $a, $db; @@ -270,7 +295,7 @@ function notifier_run($argv, $argc){ // a delivery fork. private groups (forum_mode == 2) do not uplink if((intval($parent['forum_mode']) == 1) && (! $top_level) && ($cmd !== 'uplink')) { - proc_run('php','include/notifier','uplink',$item_id); + proc_run('php','include/notifier.php','uplink',$item_id); } $conversants = array(); @@ -555,9 +580,9 @@ function notifier_run($argv, $argc){ dbesc($nickname) ); - if(count($x)) { - - if($owner['page-flags'] == PAGE_COMMUNITY && ! $x[0]['writable']) { + if($x && count($x)) { + $write_flag = ((($x[0]['rel']) && ($x[0]['rel'] != CONTACT_IS_SHARING)) ? true : false); + if((($owner['page-flags'] == PAGE_COMMUNITY) || ($write_flag)) && (! $x[0]['writable'])) { q("update contact set writable = 1 where id = %d limit 1", intval($x[0]['id']) ); diff --git a/include/oauth.php b/include/oauth.php index 2724dcf7cf..103d4c2fa9 100644 --- a/include/oauth.php +++ b/include/oauth.php @@ -145,6 +145,7 @@ class FKOAuth1 extends OAuthServer { } $_SESSION['uid'] = $record['uid']; $_SESSION['theme'] = $record['theme']; + $_SESSION['mobile-theme'] = get_pconfig($record['uid'], 'system', 'mobile_theme'); $_SESSION['authenticated'] = 1; $_SESSION['page_flags'] = $record['page-flags']; $_SESSION['my_url'] = $a->get_baseurl() . '/profile/' . $record['nickname']; diff --git a/include/oembed.php b/include/oembed.php index a4452586ee..dbb96a67ca 100755 --- a/include/oembed.php +++ b/include/oembed.php @@ -12,7 +12,9 @@ function oembed_replacecb($matches){ function oembed_fetch_url($embedurl){ - $txt = Cache::get($embedurl); + $a = get_app(); + + $txt = Cache::get($a->videowidth . $embedurl); // These media files should now be caught in bbcode.php // left here as a fallback in case this is called from another source @@ -38,7 +40,7 @@ function oembed_fetch_url($embedurl){ $entries = $xpath->query("//link[@type='application/json+oembed']"); foreach($entries as $e){ $href = $e->getAttributeNode("href")->nodeValue; - $txt = fetch_url($href . '&maxwidth=425'); + $txt = fetch_url($href . '&maxwidth=' . $a->videowidth); break; } } @@ -47,7 +49,7 @@ function oembed_fetch_url($embedurl){ if ($txt==false || $txt==""){ // try oohembed service - $ourl = "http://oohembed.com/oohembed/?url=".urlencode($embedurl).'&maxwidth=425'; + $ourl = "http://oohembed.com/oohembed/?url=".urlencode($embedurl).'&maxwidth=' . $a->videowidth; $txt = fetch_url($ourl); } @@ -55,7 +57,7 @@ function oembed_fetch_url($embedurl){ if ($txt[0]!="{") $txt='{"type":"error"}'; //save in cache - Cache::set($embedurl,$txt); + Cache::set($a->videowidth . $embedurl,$txt); } @@ -114,7 +116,7 @@ function oembed_format_object($j){ if (isset($j->provider_name)) $ret.=" on ".$j->provider_name; } else { // add for html2bbcode conversion - $ret .= ""; + $ret .= ""; } $ret.="
    "; return mb_convert_encoding($ret, 'HTML-ENTITIES', mb_detect_encoding($ret)); diff --git a/include/onepoll.php b/include/onepoll.php index 09e7bb7638..9fbef168c6 100644 --- a/include/onepoll.php +++ b/include/onepoll.php @@ -275,7 +275,7 @@ function onepoll_run($argv, $argc){ openssl_private_decrypt(hex2bin($mailconf[0]['pass']),$password,$x[0]['prvkey']); $mbox = email_connect($mailbox,$mailconf[0]['user'],$password); unset($password); - logger("Mail: Connect"); + logger("Mail: Connect to " . $mailconf[0]['user']); if($mbox) { q("UPDATE `mailacct` SET `last_check` = '%s' WHERE `id` = %d AND `uid` = %d LIMIT 1", dbesc(datetime_convert()), @@ -289,20 +289,69 @@ function onepoll_run($argv, $argc){ $msgs = email_poll($mbox,$contact['addr']); if(count($msgs)) { - logger("Mail: Parsing ".count($msgs)." mails.", LOGGER_DEBUG); + logger("Mail: Parsing ".count($msgs)." mails for ".$mailconf[0]['user'], LOGGER_DEBUG); - foreach($msgs as $msg_uid) { + $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); + + 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); +// $meta = email_msg_meta($mbox,$msg_uid); +// $headers = email_msg_headers($mbox,$msg_uid); - // look for a 'references' header and try and match with a parent item we have locally. - - $raw_refs = ((x($headers,'references')) ? str_replace("\t",'',$headers['references']) : ''); $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']) + ); + + 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; + } + + + // 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)) { @@ -314,48 +363,14 @@ function onepoll_run($argv, $argc){ intval($importer_uid) ); if(count($r)) - $datarray['parent-uri'] = $r[0]['uri']; + $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']; - // 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); - 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: - break; - case 1: - logger("Mail: Deleting ".$msg_uid); - imap_delete($mbox, $msg_uid, FT_UID); - break; - case 2: - logger("Mail: Mark as seen ".$msg_uid); - imap_setflag_full($mbox, $msg_uid, "\\Seen", ST_UID); - break; - case 3: - logger("Mail: Moving ".$msg_uid." to ".$mailconf[0]['movetofolder']); - 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; - } - // Decoding the header $subject = imap_mime_header_decode($meta->subject); $datarray['title'] = ""; @@ -377,12 +392,12 @@ function onepoll_run($argv, $argc){ $r = email_get_msg($mbox,$msg_uid, $reply); if(! $r) { - logger("Mail: can't fetch msg ".$msg_uid); + logger("Mail: can't fetch msg ".$msg_uid." for ".$mailconf[0]['user']); continue; } $datarray['body'] = escape_tags($r['body']); - logger("Mail: Importing ".$msg_uid); + 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 @@ -421,17 +436,18 @@ function onepoll_run($argv, $argc){ ); 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); + 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); + 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']); + 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); diff --git a/include/plugin.php b/include/plugin.php index ffa562273f..db3224f296 100644 --- a/include/plugin.php +++ b/include/plugin.php @@ -8,7 +8,7 @@ function uninstall_plugin($plugin){ q("DELETE FROM `addon` WHERE `name` = '%s' ", dbesc($plugin) ); - + @include_once('addon/' . $plugin . '/' . $plugin . '.php'); if(function_exists($plugin . '_uninstall')) { $func = $plugin . '_uninstall'; @@ -18,7 +18,6 @@ function uninstall_plugin($plugin){ if (! function_exists('install_plugin')){ function install_plugin($plugin) { - // silently fail if plugin was removed if(! file_exists('addon/' . $plugin . '/' . $plugin . '.php')) @@ -77,7 +76,7 @@ function reload_plugins() { $pl = trim($pl); $fname = 'addon/' . $pl . '/' . $pl . '.php'; - + if(file_exists($fname)) { $t = @filemtime($fname); foreach($installed as $i) { @@ -111,7 +110,7 @@ function reload_plugins() { if(! function_exists('register_hook')) { -function register_hook($hook,$file,$function) { +function register_hook($hook,$file,$function,$priority=0) { $r = q("SELECT * FROM `hook` WHERE `hook` = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1", dbesc($hook), @@ -121,10 +120,11 @@ function register_hook($hook,$file,$function) { if(count($r)) return true; - $r = q("INSERT INTO `hook` (`hook`, `file`, `function`) VALUES ( '%s', '%s', '%s' ) ", + $r = q("INSERT INTO `hook` (`hook`, `file`, `function`, `priority`) VALUES ( '%s', '%s', '%s', '%s' ) ", dbesc($hook), dbesc($file), - dbesc($function) + dbesc($function), + dbesc($priority) ); return $r; }} @@ -145,7 +145,7 @@ if(! function_exists('load_hooks')) { function load_hooks() { $a = get_app(); $a->hooks = array(); - $r = q("SELECT * FROM `hook` WHERE 1"); + $r = q("SELECT * FROM `hook` WHERE 1 ORDER BY `priority` DESC"); if(count($r)) { foreach($r as $rr) { if(! array_key_exists($rr['hook'],$a->hooks)) @@ -255,6 +255,7 @@ function get_theme_info($theme){ 'author' => array(), 'maintainer' => array(), 'version' => "", + 'credits' => "", 'experimental' => false, 'unsupported' => false ); diff --git a/include/queue.php b/include/queue.php index 7e92705be2..ba3babe70d 100644 --- a/include/queue.php +++ b/include/queue.php @@ -161,7 +161,7 @@ function queue_run($argv, $argc){ case NETWORK_DIASPORA: if($contact['notify']) { logger('queue: diaspora_delivery: item ' . $q_item['id'] . ' for ' . $contact['name']); - $deliver_status = diaspora_transmit($owner,$contact,$data,$public); + $deliver_status = diaspora_transmit($owner,$contact,$data,$public,true); if($deliver_status == (-1)) update_queue_time($q_item['id']); diff --git a/include/redir.php b/include/redir.php new file mode 100644 index 0000000000..3fbbf4c130 --- /dev/null +++ b/include/redir.php @@ -0,0 +1,81 @@ +user['nickname'])) + return; + + if(local_user()) { + + // We need to find out if $contact_nick is a user on this hub, and if so, if I + // am a contact of that user. However, that user may have other contacts with the + // same nickname as me on other hubs or other networks. Exclude these by requiring + // that the contact have a local URL. I will be the only person with my nickname at + // this URL, so if a result is found, then I am a contact of the $contact_nick user. + // + // We also have to make sure that I'm a legitimate contact--I'm not blocked or pending. + + $baseurl = $a->get_baseurl(); + $domain_st = strpos($baseurl, "://"); + if($domain_st === false) + return; + $baseurl = substr($baseurl, $domain_st + 3); + + $r = q("SELECT id FROM contact WHERE uid = ( SELECT uid FROM user WHERE nickname = '%s' LIMIT 1 ) + AND nick = '%s' AND self = 0 AND url LIKE '%%%s%%' AND blocked = 0 AND pending = 0 LIMIT 1", + dbesc($contact_nick), + dbesc($a->user['nickname']), + dbesc($baseurl) + ); + + if((!$r) || (! count($r)) || $r[0]['id'] == remote_user()) + return; + + + $r = q("SELECT * FROM contact WHERE nick = '%s' + AND network = '%s' AND uid = %d AND url LIKE '%%%s%%' LIMIT 1", + dbesc($contact_nick), + dbesc(NETWORK_DFRN), + intval(local_user()), + dbesc($baseurl) + ); + + if(! ($r && count($r))) + return; + + $cid = $r[0]['id']; + + $dfrn_id = $orig_id = (($r[0]['issued-id']) ? $r[0]['issued-id'] : $r[0]['dfrn-id']); + + if($r[0]['duplex'] && $r[0]['issued-id']) { + $orig_id = $r[0]['issued-id']; + $dfrn_id = '1:' . $orig_id; + } + if($r[0]['duplex'] && $r[0]['dfrn-id']) { + $orig_id = $r[0]['dfrn-id']; + $dfrn_id = '0:' . $orig_id; + } + + $sec = random_string(); + + q("INSERT INTO `profile_check` ( `uid`, `cid`, `dfrn_id`, `sec`, `expire`) + VALUES( %d, %s, '%s', '%s', %d )", + intval(local_user()), + intval($cid), + dbesc($dfrn_id), + dbesc($sec), + intval(time() + 45) + ); + + $url = curPageURL(); + + logger('auto_redir: ' . $r[0]['name'] . ' ' . $sec, LOGGER_DEBUG); + $dest = (($url) ? '&destination_url=' . $url : ''); + goaway ($r[0]['poll'] . '?dfrn_id=' . $dfrn_id + . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . '&type=profile&sec=' . $sec . $dest ); + } + + return; +} + + diff --git a/include/security.php b/include/security.php index af201d2af1..0558df1a1a 100644 --- a/include/security.php +++ b/include/security.php @@ -6,6 +6,7 @@ function authenticate_success($user_record, $login_initial = false, $interactive $_SESSION['uid'] = $user_record['uid']; $_SESSION['theme'] = $user_record['theme']; + $_SESSION['mobile-theme'] = get_pconfig($user_record['uid'], 'system', 'mobile_theme'); $_SESSION['authenticated'] = 1; $_SESSION['page_flags'] = $user_record['page-flags']; $_SESSION['my_url'] = $a->get_baseurl() . '/profile/' . $user_record['nickname']; @@ -120,12 +121,26 @@ function can_write_wall(&$a,$owner) { elseif($verified === 1) return false; else { + $cid = 0; + + if(is_array($_SESSION['remote'])) { + foreach($_SESSION['remote'] as $visitor) { + if($visitor['uid'] == $owner) { + $cid = $visitor['cid']; + break; + } + } + } + + if(! $cid) + return false; + $r = q("SELECT `contact`.*, `user`.`page-flags` FROM `contact` LEFT JOIN `user` on `user`.`uid` = `contact`.`uid` WHERE `contact`.`uid` = %d AND `contact`.`id` = %d AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 AND `user`.`blockwall` = 0 AND `readonly` = 0 AND ( `contact`.`rel` IN ( %d , %d ) OR `user`.`page-flags` = %d ) LIMIT 1", intval($owner), - intval(remote_user()), + intval($cid), intval(CONTACT_IS_SHARING), intval(CONTACT_IS_FRIEND), intval(PAGE_COMMUNITY) @@ -199,7 +214,7 @@ function permissions_sql($owner_id,$remote_verified = false,$groups = null) { $gs .= '|<' . intval($g) . '>'; } - $sql = sprintf( + /*$sql = sprintf( " AND ( allow_cid = '' OR allow_cid REGEXP '<%d>' ) AND ( deny_cid = '' OR NOT deny_cid REGEXP '<%d>' ) AND ( allow_gid = '' OR allow_gid REGEXP '%s' ) @@ -209,6 +224,16 @@ function permissions_sql($owner_id,$remote_verified = false,$groups = null) { intval($remote_user), dbesc($gs), dbesc($gs) + );*/ + $sql = sprintf( + " AND ( NOT (deny_cid REGEXP '<%d>' OR deny_gid REGEXP '%s') + AND ( allow_cid REGEXP '<%d>' OR allow_gid REGEXP '%s' OR ( allow_cid = '' AND allow_gid = '') ) + ) + ", + intval($remote_user), + dbesc($gs), + intval($remote_user), + dbesc($gs) ); } } @@ -346,3 +371,23 @@ function check_form_security_token_ForbiddenOnErr($typename = '', $formname = 'f killme(); } } + +// Returns an array of group id's this contact is a member of. +// This array will only contain group id's related to the uid of this +// DFRN contact. They are *not* neccessarily unique across the entire site. + + +if(! function_exists('init_groups_visitor')) { +function init_groups_visitor($contact_id) { + $groups = array(); + $r = q("SELECT `gid` FROM `group_member` + WHERE `contact-id` = %d ", + intval($contact_id) + ); + if(count($r)) { + foreach($r as $rr) + $groups[] = $rr['gid']; + } + return $groups; +}} + diff --git a/include/socgraph.php b/include/socgraph.php index eccb133cc6..4fe9eaf909 100644 --- a/include/socgraph.php +++ b/include/socgraph.php @@ -229,7 +229,7 @@ function count_common_friends_zcid($uid,$zcid) { } -function common_friends_zcid($uid,$zcid,$start = 0, $limit = 9999,$shuffle) { +function common_friends_zcid($uid,$zcid,$start = 0, $limit = 9999,$shuffle = false) { if($shuffle) $sql_extra = " order by rand() "; diff --git a/include/template_processor.php b/include/template_processor.php index 46252c355f..4088ddab60 100644 --- a/include/template_processor.php +++ b/include/template_processor.php @@ -63,7 +63,7 @@ if ($b[0]=="$") $b = $this->_get_var($b); $val = ($a == $b); } else if (strpos($args[2],"!=")>0){ - list($a,$b) = explode("!=",$args[2]); + list($a,$b) = array_map("trim", explode("!=",$args[2])); $a = $this->_get_var($a); if ($b[0]=="$") $b = $this->_get_var($b); $val = ($a != $b); @@ -133,6 +133,26 @@ return $ret; } + + /** + * DEBUG node + * + * {{ debug $var [$var [$var [...]]] }}{{ enddebug }} + * + * replace node with
    var_dump($var, $var, ...);
    + */ + private function _replcb_debug($args){ + $vars = array_map('trim', explode(" ",$args[2])); + $vars[] = $args[1]; + + $ret = "
    ";
    +			foreach ($vars as $var){
    +				$ret .= htmlspecialchars(var_export( $this->_get_var($var), true ));
    +				$ret .= "\n";
    +			}
    +			$ret .= "
    "; + return $ret; + } private function _replcb_node($m) { $node = $this->nodes[$m[1]]; diff --git a/include/text.php b/include/text.php index 409d40d59f..0e33c678c2 100644 --- a/include/text.php +++ b/include/text.php @@ -70,7 +70,7 @@ function notags($string) { if(! function_exists('escape_tags')) { function escape_tags($string) { - return(htmlspecialchars($string)); + return(htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false)); }} @@ -280,6 +280,31 @@ function paginate(&$a) { return $o; }} +if(! function_exists('alt_pager')) { +function alt_pager(&$a, $i) { + $o = ''; + $stripped = preg_replace('/(&page=[0-9]*)/','',$a->query_string); + $stripped = str_replace('q=','',$stripped); + $stripped = trim($stripped,'/'); + $pagenum = $a->pager['page']; + $url = $a->get_baseurl() . '/' . $stripped; + + $o .= '
    '; + + if($a->pager['page']>1) + $o .= "pager['page'] - 1).'">' . t('newer') . ''; + if($i>0) { + if($a->pager['page']>1) + $o .= " - "; + $o .= "pager['page'] + 1).'">' . t('older') . ''; + } + + + $o .= '
    '."\r\n"; + + return $o; +}} + // Turn user/group ACLs stored as angle bracketed text into arrays if(! function_exists('expand_acl')) { @@ -378,7 +403,7 @@ function load_view_file($s) { return file_get_contents("$d/$lang/$b"); $theme = current_theme(); - + if(file_exists("$d/theme/$theme/$b")) return file_get_contents("$d/theme/$theme/$b"); @@ -479,6 +504,10 @@ function get_tags($s) { $s = preg_replace('/\[code\](.*?)\[\/code\]/sm','',$s); + // ignore anything in a bbtag + + $s = preg_replace('/\[(.*?)\]/sm','',$s); + // Match full names against @tags including the space between first and last // We will look these up afterward to see if they are full names or not recognisable. @@ -681,6 +710,55 @@ function linkify($s) { return($s); }} +function get_poke_verbs() { + + // index is present tense verb + // value is array containing past tense verb, translation of present, translation of past + + $arr = array( + 'poke' => array( 'poked', t('poke'), t('poked')), + 'ping' => array( 'pinged', t('ping'), t('pinged')), + 'prod' => array( 'prodded', t('prod'), t('prodded')), + 'slap' => array( 'slapped', t('slap'), t('slapped')), + 'finger' => array( 'fingered', t('finger'), t('fingered')), + 'rebuff' => array( 'rebuffed', t('rebuff'), t('rebuffed')), + ); + call_hooks('poke_verbs', $arr); + return $arr; +} + +function get_mood_verbs() { + + // index is present tense verb + // value is array containing past tense verb, translation of present, translation of past + + $arr = array( + 'happy' => t('happy'), + 'sad' => t('sad'), + 'mellow' => t('mellow'), + 'tired' => t('tired'), + 'perky' => t('perky'), + 'angry' => t('angry'), + 'stupefied' => t('stupified'), + 'puzzled' => t('puzzled'), + 'interested' => t('interested'), + 'bitter' => t('bitter'), + 'cheerful' => t('cheerful'), + 'alive' => t('alive'), + 'annoyed' => t('annoyed'), + 'anxious' => t('anxious'), + 'cranky' => t('cranky'), + 'disturbed' => t('disturbed'), + 'frustrated' => t('frustrated'), + 'motivated' => t('motivated'), + 'relaxed' => t('relaxed'), + 'surprised' => t('surprised'), + ); + + call_hooks('mood_verbs', $arr); + return $arr; +} + /** * @@ -748,7 +826,6 @@ function smilies($s, $sample = false) { ':facepalm', ':like', ':dislike', - '~friendika', '~friendica' ); @@ -786,7 +863,6 @@ function smilies($s, $sample = false) { ':facepalm', ':like', ':dislike', - '~friendika ~friendika', '~friendica ~friendica' ); @@ -927,7 +1003,7 @@ function prepare_body($item,$attach = false) { } $title = ((strlen(trim($mtch[4]))) ? escape_tags(trim($mtch[4])) : escape_tags($mtch[1])); $title .= ' ' . $mtch[2] . ' ' . t('bytes'); - if((local_user() == $item['uid']) && $item['contact-id'] != $a->contact['id']) + if((local_user() == $item['uid']) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN)) $the_url = $a->get_baseurl() . '/redir/' . $item['contact-id'] . '?f=1&url=' . $mtch[1]; else $the_url = $mtch[1]; @@ -938,35 +1014,8 @@ function prepare_body($item,$attach = false) { } $s .= '
    '; } - $matches = false; - $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER); - if($cnt) { -// logger('prepare_text: categories: ' . print_r($matches,true), LOGGER_DEBUG); - foreach($matches as $mtch) { - if(strlen($x)) - $x .= ','; - $x .= xmlify(file_tag_decode($mtch[1])) - . ((local_user() == $item['uid']) ? ' ' . t('[remove]') . '' : ''); - } - if(strlen($x)) - $s .= '
    ' . t('Categories:') . ' ' . $x . '
    '; - } - $matches = false; - $x = ''; - $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER); - if($cnt) { -// logger('prepare_text: filed_under: ' . print_r($matches,true), LOGGER_DEBUG); - foreach($matches as $mtch) { - if(strlen($x)) - $x .= '   '; - $x .= xmlify(file_tag_decode($mtch[1])) . ' ' . t('[remove]') . ''; - } - if(strlen($x) && (local_user() == $item['uid'])) - $s .= '
    ' . t('Filed under:') . ' ' . $x . '
    '; - } - // Look for spoiler $spoilersearch = '
    '; @@ -1020,6 +1069,73 @@ function prepare_text($text) { }} +/** + * returns + * [ + * //categories [ + * { + * 'name': 'category name', + * 'removeurl': 'url to remove this category', + * 'first': 'is the first in this array? true/false', + * 'last': 'is the last in this array? true/false', + * } , + * .... + * ], + * // folders [ + * 'name': 'folder name', + * 'removeurl': 'url to remove this folder', + * 'first': 'is the first in this array? true/false', + * 'last': 'is the last in this array? true/false', + * } , + * .... + * ] + * ] + */ +function get_cats_and_terms($item) { + $a = get_app(); + $categories = array(); + $folders = array(); + + $matches = false; $first = true; + $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $categories[] = array( + 'name' => xmlify(file_tag_decode($mtch[1])), + 'url' => "#", + 'removeurl' => ((local_user() == $item['uid'])?$a->get_baseurl() . '/filerm/' . $item['id'] . '?f=&cat=' . xmlify(file_tag_decode($mtch[1])):""), + 'first' => $first, + 'last' => false + ); + $first = false; + } + } + if (count($categories)) $categories[count($categories)-1]['last'] = true; + + + if(local_user() == $item['uid']) { + $matches = false; $first = true; + $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $folders[] = array( + 'name' => xmlify(file_tag_decode($mtch[1])), + 'url' => "#", + 'removeurl' => ((local_user() == $item['uid'])?$a->get_baseurl() . '/filerm/' . $item['id'] . '?f=&term=' . xmlify(file_tag_decode($mtch[1])):""), + 'first' => $first, + 'last' => false + ); + $first = false; + } + } + } + + if (count($folders)) $folders[count($folders)-1]['last'] = true; + + return array($categories, $folders); +} + + /** * return atom link elements for all of our hubs */ @@ -1537,6 +1653,7 @@ function undo_post_tagging($s) { function fix_mce_lf($s) { $s = str_replace("\r\n","\n",$s); +// $s = str_replace("\n\n","\n",$s); return $s; } diff --git a/include/user.php b/include/user.php index 039b30bbd1..282bbdbba2 100644 --- a/include/user.php +++ b/include/user.php @@ -277,6 +277,26 @@ function create_user($arr) { require_once('include/group.php'); group_add($newuid, t('Friends')); + $r = q("SELECT id FROM `group` WHERE uid = %d AND name = '%s'", + intval($newuid), + dbesc(t('Friends')) + ); + if($r && count($r)) { + $def_gid = $r[0]['id']; + + q("UPDATE user SET def_gid = %d WHERE uid = %d", + intval($r[0]['id']), + intval($newuid) + ); + } + + if(get_config('system', 'newuser_private') && $def_gid) { + q("UPDATE user SET allow_gid = '%s' WHERE uid = %d", + dbesc("<" . $def_gid . ">"), + intval($newuid) + ); + } + } // if we have no OpenID photo try to look up an avatar diff --git a/index.php b/index.php index 61f3562b58..7d7674530a 100644 --- a/index.php +++ b/index.php @@ -13,8 +13,10 @@ */ require_once('boot.php'); +require_once('object/BaseObject.php'); $a = new App; +BaseObject::set_app($a); /** * @@ -27,6 +29,8 @@ $install = ((file_exists('.htconfig.php') && filesize('.htconfig.php')) ? false @include(".htconfig.php"); + + $lang = get_browser_language(); load_translation_table($lang); @@ -118,6 +122,12 @@ if(! x($_SESSION,'authenticated')) $a->init_pagehead(); +/** + * Build the page ending -- this is stuff that goes right before + * the closing tag + */ + +$a->init_page_end(); if(! x($_SESSION,'sysmsg')) @@ -358,6 +368,19 @@ if($a->module != 'install') { $a->page['htmlhead'] = replace_macros($a->page['htmlhead'], array('$stylesheet' => current_theme_url())); +if($a->is_mobile || $a->is_tablet) { + if(isset($_SESSION['show-mobile']) && !$_SESSION['show-mobile']) { + $link = $a->get_baseurl() . '/toggle_mobile?address=' . curPageURL(); + } + else { + $link = $a->get_baseurl() . '/toggle_mobile?off=1&address=' . curPageURL(); + } + $a->page['footer'] = replace_macros(get_markup_template("toggle_mobile_footer.tpl"), array( + '$toggle_link' => $link, + '$toggle_text' => t('toggle mobile') + )); +} + $page = $a->page; $profile = $a->profile; diff --git a/js/acl.min.js b/js/acl.min.js new file mode 100644 index 0000000000..74904a02c0 --- /dev/null +++ b/js/acl.min.js @@ -0,0 +1 @@ +function ACL(e,t){that=this,that.url=e,that.kp_timer=null,t==undefined&&(t=[]),that.allow_cid=t[0]||[],that.allow_gid=t[1]||[],that.deny_cid=t[2]||[],that.deny_gid=t[3]||[],that.group_uids=[],that.nw=4,that.list_content=$("#acl-list-content"),that.item_tpl=unescape($(".acl-list-item[rel=acl-template]").html()),that.showall=$("#acl-showall"),t.length==0&&that.showall.addClass("selected"),that.showall.click(that.on_showall),$(".acl-button-show").live("click",that.on_button_show),$(".acl-button-hide").live("click",that.on_button_hide),$("#acl-search").keypress(that.on_search),$("#acl-wrapper").parents("form").submit(that.on_submit),that.get(0,100)}ACL.prototype.on_submit=function(){aclfileds=$("#acl-fields").html(""),$(that.allow_gid).each(function(e,t){aclfileds.append("")}),$(that.allow_cid).each(function(e,t){aclfileds.append("")}),$(that.deny_gid).each(function(e,t){aclfileds.append("")}),$(that.deny_cid).each(function(e,t){aclfileds.append("")})},ACL.prototype.search=function(){var e=$("#acl-search").val();that.list_content.html(""),that.get(0,100,e)},ACL.prototype.on_search=function(e){that.kp_timer&&clearTimeout(that.kp_timer),that.kp_timer=setTimeout(that.search,1e3)},ACL.prototype.on_showall=function(e){return e.preventDefault(),e.stopPropagation(),that.showall.hasClass("selected")?!1:(that.showall.addClass("selected"),that.allow_cid=[],that.allow_gid=[],that.deny_cid=[],that.deny_gid=[],that.update_view(),!1)},ACL.prototype.on_button_show=function(e){return e.preventDefault(),e.stopImmediatePropagation(),e.stopPropagation(),that.set_allow($(this).parent().attr("id")),!1},ACL.prototype.on_button_hide=function(e){return e.preventDefault(),e.stopImmediatePropagation(),e.stopPropagation(),that.set_deny($(this).parent().attr("id")),!1},ACL.prototype.set_allow=function(e){type=e[0],id=parseInt(e.substr(1));switch(type){case"g":that.allow_gid.indexOf(id)<0?that.allow_gid.push(id):that.allow_gid.remove(id),that.deny_gid.indexOf(id)>=0&&that.deny_gid.remove(id);break;case"c":that.allow_cid.indexOf(id)<0?that.allow_cid.push(id):that.allow_cid.remove(id),that.deny_cid.indexOf(id)>=0&&that.deny_cid.remove(id)}that.update_view()},ACL.prototype.set_deny=function(e){type=e[0],id=parseInt(e.substr(1));switch(type){case"g":that.deny_gid.indexOf(id)<0?that.deny_gid.push(id):that.deny_gid.remove(id),that.allow_gid.indexOf(id)>=0&&that.allow_gid.remove(id);break;case"c":that.deny_cid.indexOf(id)<0?that.deny_cid.push(id):that.deny_cid.remove(id),that.allow_cid.indexOf(id)>=0&&that.allow_cid.remove(id)}that.update_view()},ACL.prototype.update_view=function(){that.allow_gid.length==0&&that.allow_cid.length==0&&that.deny_gid.length==0&&that.deny_cid.length==0?(that.showall.addClass("selected"),$("#jot-perms-icon").removeClass("lock").addClass("unlock"),$("#jot-public").show(),$(".profile-jot-net input").attr("disabled",!1),typeof editor!="undefined"&&editor!=0&&$("#profile-jot-desc").html(ispublic)):(that.showall.removeClass("selected"),$("#jot-perms-icon").removeClass("unlock").addClass("lock"),$("#jot-public").hide(),$(".profile-jot-net input").attr("disabled","disabled"),$("#profile-jot-desc").html(" ")),$("#acl-list-content .acl-list-item").each(function(){$(this).removeClass("groupshow grouphide")}),$("#acl-list-content .acl-list-item").each(function(){itemid=$(this).attr("id"),type=itemid[0],id=parseInt(itemid.substr(1)),btshow=$(this).children(".acl-button-show").removeClass("selected"),bthide=$(this).children(".acl-button-hide").removeClass("selected");switch(type){case"g":var e="";that.allow_gid.indexOf(id)>=0&&(btshow.addClass("selected"),bthide.removeClass("selected"),e="groupshow"),that.deny_gid.indexOf(id)>=0&&(btshow.removeClass("selected"),bthide.addClass("selected"),e="grouphide"),$(that.group_uids[id]).each(function(t,n){e=="grouphide"&&$("#c"+n).removeClass("groupshow");if(e!=""){var r=$("#c"+n).attr("class");if(r==undefined)return!0;var i=r.indexOf("grouphide");i==-1&&$("#c"+n).addClass(e)}});break;case"c":that.allow_cid.indexOf(id)>=0&&(btshow.addClass("selected"),bthide.removeClass("selected")),that.deny_cid.indexOf(id)>=0&&(btshow.removeClass("selected"),bthide.addClass("selected"))}})},ACL.prototype.get=function(e,t,n){var r={start:e,count:t,search:n};$.ajax({type:"POST",url:that.url,data:r,dataType:"json",success:that.populate})},ACL.prototype.populate=function(e){var t=Math.ceil(e.tot/that.nw)*42;that.list_content.height(t),$(e.items).each(function(){html="
    "+that.item_tpl+"
    ",html=html.format(this.photo,this.name,this.type,this.id,"",this.network,this.link),this.uids!=undefined&&(that.group_uids[this.id]=this.uids),that.list_content.append(html)}),that.update_view()}; \ No newline at end of file diff --git a/js/ajaxupload.min.js b/js/ajaxupload.min.js new file mode 100644 index 0000000000..246f2bdb2c --- /dev/null +++ b/js/ajaxupload.min.js @@ -0,0 +1,6 @@ +/** + * AJAX Upload ( http://valums.com/ajax-upload/ ) + * Copyright (c) Andris Valums + * Licensed under the MIT license ( http://valums.com/mit-license/ ) + * Thanks to Gary Haran, David Mark, Corey Burns and others for contributions. + */(function(){function log(){typeof console!="undefined"&&typeof console.log=="function"&&(Array.prototype.unshift.call(arguments,"[Ajax Upload]"),console.log(Array.prototype.join.call(arguments," ")))}function addEvent(e,t,n){if(e.addEventListener)e.addEventListener(t,n,!1);else{if(!e.attachEvent)throw new Error("not supported or DOM not loaded");e.attachEvent("on"+t,function(){n.call(e)})}}function addResizeEvent(e){var t;addEvent(window,"resize",function(){t&&clearTimeout(t),t=setTimeout(e,100)})}function getBox(e){var t,n,r,i,s=getOffset(e);return t=s.left,r=s.top,n=t+e.offsetWidth,i=r+e.offsetHeight,{left:t,right:n,top:r,bottom:i}}function addStyles(e,t){for(var n in t)t.hasOwnProperty(n)&&(e.style[n]=t[n])}function copyLayout(e,t){var n=getBox(e);addStyles(t,{position:"absolute",left:n.left+"px",top:n.top+"px",width:e.offsetWidth+"px",height:e.offsetHeight+"px"}),t.title=e.title}function fileFromPath(e){return e.replace(/.*(\/|\\)/,"")}function getExt(e){return-1!==e.indexOf(".")?e.replace(/.*[.]/,""):""}function hasClass(e,t){var n=new RegExp("\\b"+t+"\\b");return n.test(e.className)}function addClass(e,t){hasClass(e,t)||(e.className+=" "+t)}function removeClass(e,t){var n=new RegExp("\\b"+t+"\\b");e.className=e.className.replace(n,"")}function removeNode(e){e.parentNode.removeChild(e)}if(document.documentElement.getBoundingClientRect)var getOffset=function(e){var t=e.getBoundingClientRect(),n=e.ownerDocument,r=n.body,i=n.documentElement,s=i.clientTop||r.clientTop||0,o=i.clientLeft||r.clientLeft||0,u=1;if(r.getBoundingClientRect){var a=r.getBoundingClientRect();u=(a.right-a.left)/r.clientWidth}u>1&&(s=0,o=0);var f=t.top/u+(window.pageYOffset||i&&i.scrollTop/u||r.scrollTop/u)-s,l=t.left/u+(window.pageXOffset||i&&i.scrollLeft/u||r.scrollLeft/u)-o;return{top:f,left:l}};else var getOffset=function(e){var t=0,n=0;do t+=e.offsetTop||0,n+=e.offsetLeft||0,e=e.offsetParent;while(e);return{left:n,top:t}};var toElement=function(){var e=document.createElement("div");return function(t){e.innerHTML=t;var n=e.firstChild;return e.removeChild(n)}}(),getUID=function(){var e=0;return function(){return"ValumsAjaxUpload"+e++}}();window.AjaxUpload=function(e,t){this._settings={action:"upload.php",name:"userfile",data:{},autoSubmit:!0,responseType:!1,hoverClass:"hover",focusClass:"focus",disabledClass:"disabled",onChange:function(e,t){},onSubmit:function(e,t){},onComplete:function(e,t){}};for(var n in t)t.hasOwnProperty(n)&&(this._settings[n]=t[n]);e.jquery?e=e[0]:typeof e=="string"&&(/^#.*/.test(e)&&(e=e.slice(1)),e=document.getElementById(e));if(!e||e.nodeType!==1)throw new Error("Please make sure that you're passing a valid element");e.nodeName.toUpperCase()=="A"&&addEvent(e,"click",function(e){e&&e.preventDefault?e.preventDefault():window.event&&(window.event.returnValue=!1)}),this._button=e,this._input=null,this._disabled=!1,this.enable(),this._rerouteClicks()},AjaxUpload.prototype={setData:function(e){this._settings.data=e},disable:function(){addClass(this._button,this._settings.disabledClass),this._disabled=!0;var e=this._button.nodeName.toUpperCase();(e=="INPUT"||e=="BUTTON")&&this._button.setAttribute("disabled","disabled"),this._input&&(this._input.parentNode.style.visibility="hidden")},enable:function(){removeClass(this._button,this._settings.disabledClass),this._button.removeAttribute("disabled"),this._disabled=!1},_createInput:function(){var e=this,t=document.createElement("input");t.setAttribute("type","file"),t.setAttribute("name",this._settings.name),addStyles(t,{position:"absolute",right:0,margin:0,padding:0,fontSize:"480px",fontFamily:"sans-serif",cursor:"pointer"});var n=document.createElement("div");addStyles(n,{display:"block",position:"absolute",overflow:"hidden",margin:0,padding:0,opacity:0,direction:"ltr",zIndex:2147483583,cursor:"pointer"});if(n.style.opacity!=="0"){if(typeof n.filters=="undefined")throw new Error("Opacity not supported by the browser");n.style.filter="alpha(opacity=0)"}addEvent(t,"change",function(){if(!t||t.value==="")return;var n=fileFromPath(t.value);if(!1===e._settings.onChange.call(e,n,getExt(n))){e._clearInput();return}e._settings.autoSubmit&&e.submit()}),addEvent(t,"mouseover",function(){addClass(e._button,e._settings.hoverClass)}),addEvent(t,"mouseout",function(){removeClass(e._button,e._settings.hoverClass),removeClass(e._button,e._settings.focusClass),t.parentNode.style.visibility="hidden"}),addEvent(t,"focus",function(){addClass(e._button,e._settings.focusClass)}),addEvent(t,"blur",function(){removeClass(e._button,e._settings.focusClass)}),n.appendChild(t),document.body.appendChild(n),this._input=t},_clearInput:function(){if(!this._input)return;removeNode(this._input.parentNode),this._input=null,this._createInput(),removeClass(this._button,this._settings.hoverClass),removeClass(this._button,this._settings.focusClass)},_rerouteClicks:function(){var e=this;addEvent(e._button,"mouseover",function(){if(e._disabled)return;e._input||e._createInput();var t=e._input.parentNode;copyLayout(e._button,t),t.style.visibility="visible"})},_createIframe:function(){var e=getUID(),t=toElement('