1
0
Fork 0

Merge remote-tracking branch 'upstream/develop' into develop

Also removed <<<<< as this interfers (a bit) with searching for merge conflicts
with a more simplier editor.

Signed-off-by: Roland Häder <roland@mxchange.org>

Conflicts:
	mod/ping.php
	view/lang/fr/messages.po
	view/lang/fr/strings.php
This commit is contained in:
Roland Häder 2016-12-13 09:59:43 +01:00
commit d489ba1510
1261 changed files with 66596 additions and 191430 deletions

6
.gitignore vendored
View file

@ -42,3 +42,9 @@ nbproject
#ignore local folder
/local/
#ignore config files from Visual Studio
/.vs/
/php_friendica.phpproj
/php_friendica.sln
/php_friendica.phpproj.user

View file

@ -1,5 +1,5 @@
Friendica Communications Server
Copyright (c) 2010-2013 the Friendica Project
Copyright (c) 2010-2016 the Friendica Project
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by

View file

@ -24,12 +24,12 @@ If you want to get your work into the source tree yourself, feel free to do so a
The process is simple and friendica ships with all the tools necessary.
The location of the translated files in the source tree is
/view/LNG-CODE/
/view/lang/LNG-CODE/
where LNG-CODE is the language code used, e.g. de for German or fr for French.
The translated strings come as a "message.po" file from transifex which needs to be translated into the PHP file friendica uses.
To do so, place the file in the directory mentioned above and use the "po2php" utility from the util directory of your friendica installation.
Assuming you want to convert the German localization which is placed in view/de/message.po you would do the following.
Assuming you want to convert the German localization which is placed in view/lang/de/message.po you would do the following.
1. Navigate at the command prompt to the base directory of your
friendica installation
@ -37,9 +37,9 @@ Assuming you want to convert the German localization which is placed in view/de/
2. Execute the po2php script, which will place the translation
in the strings.php file that is used by friendica.
$> php util/po2php.php view/de/messages.po
$> php util/po2php.php view/lang/de/messages.po
The output of the script will be placed at view/de/strings.php where
The output of the script will be placed at view/lang/de/strings.php where
friendica is expecting it, so you can test your translation immediately.
3. Visit your friendica page to check if it still works in the language you
@ -50,7 +50,7 @@ Assuming you want to convert the German localization which is placed in view/de/
not give any output if the file is ok but might give a hint for
searching the bug in the file.
$> php view/de/strings.php
$> php view/lang/de/strings.php
4. commit the two files with a meaningful commit message to your git
repository, push it to your fork of the friendica repository at github and

52
Vagrantfile vendored
View file

@ -1,31 +1,55 @@
server_ip = "192.168.22.10"
server_memory = "384" # MB
server_ip_trusty = "192.168.22.10"
server_ip_xenial = "192.168.22.11"
server_memory = "1024" # MB
server_timezone = "UTC"
public_folder = "/vagrant"
Vagrant.configure(2) do |config|
######################################################################
# Set server to Ubuntu 14.04
config.vm.box = "ubuntu/trusty64"
config.vm.define "trusty" do |trusty|
trusty.vm.box = "ubuntu/trusty64"
# Disable automatic box update checking. If you disable this, then
# boxes will only be checked for updates when the user runs
# `vagrant box outdated`. This is not recommended.
# config.vm.box_check_update = false
# Disable automatic box update checking. If you disable this, then
# boxes will only be checked for updates when the user runs
# `vagrant box outdated`. This is not recommended.
# config.vm.box_check_update = false
# Create a hostname, don't forget to put it to the `hosts` file
# This will point to the server's default virtual host
# TO DO: Make this work with virtualhost along-side xip.io URL
config.vm.hostname = "friendica.dev"
# Create a hostname, don't forget to put it to the `hosts` file
# This will point to the server's default virtual host
# TO DO: Make this work with virtualhost along-side xip.io URL
trusty.vm.hostname = "friendica-trusty.dev"
# Create a static IP
config.vm.network :private_network, ip: server_ip
# Create a static IP
trusty.vm.network :private_network, ip: server_ip_trusty
end
######################################################################
# Set server to Ubuntu 16.04
config.vm.define "xenial" do |xenial|
xenial.vm.box = "boxcutter/ubuntu1604"
# Disable automatic box update checking. If you disable this, then
# boxes will only be checked for updates when the user runs
# `vagrant box outdated`. This is not recommended.
# config.vm.box_check_update = false
# Create a hostname, don't forget to put it to the `hosts` file
# This will point to the server's default virtual host
# TO DO: Make this work with virtualhost along-side xip.io URL
xenial.vm.hostname = "friendica-xenial.dev"
# Create a static IP
xenial.vm.network :private_network, ip: server_ip_xenial
end
######################################################################
# Share a folder between host and guest
config.vm.synced_folder "./", "/vagrant/", owner: "www-data", group: "vagrant"
# Provider-specific configuration so you can fine-tune various
# backing providers for Vagrant. These expose provider-specific options.
config.vm.provider "virtualbox" do |vb|

257
boot.php
View file

@ -36,9 +36,9 @@ require_once('include/dbstructure.php');
define ( 'FRIENDICA_PLATFORM', 'Friendica');
define ( 'FRIENDICA_CODENAME', 'Asparagus');
define ( 'FRIENDICA_VERSION', '3.5' );
define ( 'FRIENDICA_VERSION', '3.5.1-dev' );
define ( 'DFRN_PROTOCOL_VERSION', '2.23' );
define ( 'DB_UPDATE_VERSION', 1202 );
define ( 'DB_UPDATE_VERSION', 1209 );
/**
* @brief Constant with a HTML line break.
@ -127,6 +127,10 @@ define ( 'CACHE_MONTH', 0 );
define ( 'CACHE_WEEK', 1 );
define ( 'CACHE_DAY', 2 );
define ( 'CACHE_HOUR', 3 );
define ( 'CACHE_HALF_HOUR', 4 );
define ( 'CACHE_QUARTER_HOUR', 5 );
define ( 'CACHE_FIVE_MINUTES', 6 );
define ( 'CACHE_MINUTE', 7 );
/* @}*/
/**
@ -181,6 +185,28 @@ define ( 'PAGE_BLOG', 4 );
define ( 'PAGE_PRVGROUP', 5 );
/** @}*/
/**
* @name account types
*
* ACCOUNT_TYPE_PERSON - the account belongs to a person
* Associated page types: PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE
*
* ACCOUNT_TYPE_ORGANISATION - the account belongs to an organisation
* Associated page type: PAGE_SOAPBOX
*
* ACCOUNT_TYPE_NEWS - the account is a news reflector
* Associated page type: PAGE_SOAPBOX
*
* ACCOUNT_TYPE_COMMUNITY - the account is community forum
* Associated page types: PAGE_COMMUNITY, PAGE_PRVGROUP
* @{
*/
define ( 'ACCOUNT_TYPE_PERSON', 0 );
define ( 'ACCOUNT_TYPE_ORGANISATION',1 );
define ( 'ACCOUNT_TYPE_NEWS', 2 );
define ( 'ACCOUNT_TYPE_COMMUNITY', 3 );
/** @}*/
/**
* @name CP
*
@ -504,6 +530,7 @@ class App {
public $videoheight = 350;
public $force_max_items = 0;
public $theme_thread_allow = true;
public $theme_richtext_editor = true;
public $theme_events_in_profile = true;
/**
@ -583,6 +610,7 @@ class App {
$this->performance["markstart"] = microtime(true);
$this->callstack["database"] = array();
$this->callstack["database_write"] = array();
$this->callstack["network"] = array();
$this->callstack["file"] = array();
$this->callstack["rendering"] = array();
@ -759,60 +787,100 @@ class App {
return($this->scheme);
}
/**
* @brief Retrieves the Friendica instance base URL
*
* This function assembles the base URL from multiple parts:
* - Protocol is determined either by the request or a combination of
* system.ssl_policy and the $ssl parameter.
* - Host name is determined either by system.hostname or inferred from request
* - Path is inferred from SCRIPT_NAME
*
* Caches the result (depending on $ssl value) for performance.
*
* Note: $ssl parameter value doesn't directly correlate with the resulting protocol
*
* @param bool $ssl Whether to append http or https under SSL_POLICY_SELFSIGN
* @return string Friendica server base URL
*/
function get_baseurl($ssl = false) {
// Is the function called statically?
if (!is_object($this))
return(self::$a->get_baseurl($ssl));
if (!is_object($this)) {
return self::$a->get_baseurl($ssl);
}
// Arbitrary values, the resulting url protocol can be different
$cache_index = $ssl ? 'https' : 'http';
// Cached value found, nothing to process
if (isset($this->baseurl[$cache_index])) {
return $this->baseurl[$cache_index];
}
$scheme = $this->scheme;
if((x($this->config,'system')) && (x($this->config['system'],'ssl_policy'))) {
if(intval($this->config['system']['ssl_policy']) === intval(SSL_POLICY_FULL))
if ((x($this->config, 'system')) && (x($this->config['system'], 'ssl_policy'))) {
if (intval($this->config['system']['ssl_policy']) === SSL_POLICY_FULL) {
$scheme = 'https';
}
// Basically, we have $ssl = true on any links which can only be seen by a logged in user
// (and also the login link). Anything seen by an outsider will have it turned off.
if($this->config['system']['ssl_policy'] == SSL_POLICY_SELFSIGN) {
if($ssl)
if ($this->config['system']['ssl_policy'] == SSL_POLICY_SELFSIGN) {
if ($ssl) {
$scheme = 'https';
else
} else {
$scheme = 'http';
}
}
}
if (get_config('config','hostname') != "")
$this->hostname = get_config('config','hostname');
if (get_config('config', 'hostname') != '') {
$this->hostname = get_config('config', 'hostname');
}
$this->baseurl = $scheme . "://" . $this->hostname . ((isset($this->path) && strlen($this->path)) ? '/' . $this->path : '' );
return $this->baseurl;
$this->baseurl[$cache_index] = $scheme . "://" . $this->hostname . ((isset($this->path) && strlen($this->path)) ? '/' . $this->path : '' );
return $this->baseurl[$cache_index];
}
/**
* @brief Initializes the baseurl components
*
* Clears the baseurl cache to prevent inconstistencies
*
* @param string $url
*/
function set_baseurl($url) {
$parsed = @parse_url($url);
$this->baseurl = $url;
$this->baseurl = [];
if($parsed) {
$this->scheme = $parsed['scheme'];
$hostname = $parsed['host'];
if(x($parsed,'port'))
if (x($parsed, 'port')) {
$hostname .= ':' . $parsed['port'];
if(x($parsed,'path'))
$this->path = trim($parsed['path'],'\\/');
}
if (x($parsed, 'path')) {
$this->path = trim($parsed['path'], '\\/');
}
if (file_exists(".htpreconfig.php"))
if (file_exists(".htpreconfig.php")) {
@include(".htpreconfig.php");
}
if (get_config('config','hostname') != "")
$this->hostname = get_config('config','hostname');
if (get_config('config', 'hostname') != '') {
$this->hostname = get_config('config', 'hostname');
}
if (!isset($this->hostname) OR ($this->hostname == ""))
if (!isset($this->hostname) OR ($this->hostname == '')) {
$this->hostname = $hostname;
}
}
}
function get_hostname() {
@ -975,20 +1043,28 @@ class App {
/**
* @brief Removes the baseurl from an url. This avoids some mixed content problems.
*
* @param string $url
* @param string $orig_url
*
* @return string The cleaned url
*/
function remove_baseurl($url){
function remove_baseurl($orig_url){
// Is the function called statically?
if (!is_object($this))
return(self::$a->remove_baseurl($url));
if (!is_object($this)) {
return(self::$a->remove_baseurl($orig_url));
}
$url = normalise_link($url);
// Remove the hostname from the url if it is an internal link
$nurl = normalise_link($orig_url);
$base = normalise_link($this->get_baseurl());
$url = str_replace($base."/", "", $url);
return $url;
$url = str_replace($base."/", "", $nurl);
// if it is an external link return the orignal value
if ($url == normalise_link($orig_url)) {
return $orig_url;
} else {
return $url;
}
}
/**
@ -1078,6 +1154,9 @@ class App {
}
function save_timestamp($stamp, $value) {
if (!isset($this->config['system']['profiler']) || !$this->config['system']['profiler'])
return;
$duration = (float)(microtime(true)-$stamp);
if (!isset($this->performance[$value])) {
@ -1109,23 +1188,33 @@ class App {
$this->remove_inactive_processes();
q("START TRANSACTION");
$r = q("SELECT `pid` FROM `process` WHERE `pid` = %d", intval(getmypid()));
if(!dbm::is_result($r))
if(!dbm::is_result($r)) {
q("INSERT INTO `process` (`pid`,`command`,`created`) VALUES (%d, '%s', '%s')",
intval(getmypid()),
dbesc($command),
dbesc(datetime_convert()));
}
q("COMMIT");
}
/**
* @brief Remove inactive processes
*/
function remove_inactive_processes() {
q("START TRANSACTION");
$r = q("SELECT `pid` FROM `process`");
if(dbm::is_result($r))
foreach ($r AS $process)
if (!posix_kill($process["pid"], 0))
if(dbm::is_result($r)) {
foreach ($r AS $process) {
if (!posix_kill($process["pid"], 0)) {
q("DELETE FROM `process` WHERE `pid` = %d", intval($process["pid"]));
}
}
}
q("COMMIT");
}
/**
@ -1154,11 +1243,6 @@ class App {
return implode(", ", $callstack);
}
function mark_timestamp($mark) {
//$this->performance["markstart"] -= microtime(true) - $this->performance["marktime"];
$this->performance["markstart"] = microtime(true) - $this->performance["markstart"] - $this->performance["marktime"];
}
function get_useragent() {
return(FRIENDICA_PLATFORM." '".FRIENDICA_CODENAME."' ".FRIENDICA_VERSION."-".DB_UPDATE_VERSION."; ".$this->get_baseurl());
}
@ -1302,8 +1386,12 @@ class App {
function proc_run($args) {
if (!function_exists("proc_open")) {
return;
}
// Add the php path if it is a php call
if (count($args) && ($args[0] === 'php' OR is_int($args[0]))) {
if (count($args) && ($args[0] === 'php' OR !is_string($args[0]))) {
// If the last worker fork was less than 10 seconds before then don't fork another one.
// This should prevent the forking of masses of workers.
@ -1823,11 +1911,12 @@ function get_max_import_size() {
* @brief Wrap calls to proc_close(proc_open()) and call hook
* so plugins can take part in process :)
*
* @param (string|integer) $cmd program to run or priority
* @param (string|integer|array) $cmd program to run, priority or parameter array
*
* next args are passed as $cmd command line
* e.g.: proc_run("ls","-la","/tmp");
* or: proc_run(PRIORITY_HIGH, "include/notifier.php", "drop", $drop_id);
* or: proc_run(array('priority' => PRIORITY_HIGH, 'dont_fork' => true), "include/create_shadowentry.php", $post_id);
*
* @note $cmd and string args are surrounded with ""
*
@ -1838,24 +1927,31 @@ function proc_run($cmd){
$a = get_app();
$args = func_get_args();
$proc_args = func_get_args();
$newargs = array();
if (!count($args))
$args = array();
if (!count($proc_args)) {
return;
// expand any arrays
foreach($args as $arg) {
if(is_array($arg)) {
foreach($arg as $n) {
$newargs[] = $n;
}
} else
$newargs[] = $arg;
}
$args = $newargs;
// Preserve the first parameter
// It could contain a command, the priority or an parameter array
// If we use the parameter array we have to protect it from the following function
$run_parameter = array_shift($proc_args);
// expand any arrays
foreach ($proc_args as $arg) {
if (is_array($arg)) {
foreach ($arg as $n) {
$args[] = $n;
}
} else {
$args[] = $arg;
}
}
// Now we add the run parameters back to the array
array_unshift($args, $run_parameter);
$arr = array('args' => $args, 'run_cmd' => true);
@ -1863,16 +1959,24 @@ function proc_run($cmd){
if (!$arr['run_cmd'] OR !count($args))
return;
if (!get_config("system", "worker") OR
(($args[0] != 'php') AND !is_int($args[0]))) {
if (!get_config("system", "worker") OR (is_string($run_parameter) AND ($run_parameter != 'php'))) {
$a->proc_run($args);
return;
}
if (is_int($args[0]))
$priority = $args[0];
else
$priority = PRIORITY_MEDIUM;
$priority = PRIORITY_MEDIUM;
$dont_fork = get_config("system", "worker_dont_fork");
if (is_int($run_parameter)) {
$priority = $run_parameter;
} elseif (is_array($run_parameter)) {
if (isset($run_parameter['priority'])) {
$priority = $run_parameter['priority'];
}
if (isset($run_parameter['dont_fork'])) {
$dont_fork = $run_parameter['dont_fork'];
}
}
$argv = $args;
array_shift($argv);
@ -1889,8 +1993,9 @@ function proc_run($cmd){
intval($priority));
// Should we quit and wait for the poller to be called as a cronjob?
if (get_config("system", "worker_dont_fork"))
if ($dont_fork) {
return;
}
// Checking number of workers
$workers = q("SELECT COUNT(*) AS `workers` FROM `workerqueue` WHERE `executed` != '0000-00-00 00:00:00'");
@ -2260,6 +2365,36 @@ function get_lockpath() {
return "";
}
/**
* @brief Returns the path where spool files are stored
*
* @return string Spool path
*/
function get_spoolpath() {
$spoolpath = get_config('system','spoolpath');
if (($spoolpath != "") AND is_dir($spoolpath) AND is_writable($spoolpath)) {
return($spoolpath);
}
$temppath = get_temppath();
if ($temppath != "") {
$spoolpath = $temppath."/spool";
if (!is_dir($spoolpath)) {
mkdir($spoolpath);
} elseif (!is_writable($spoolpath)) {
$spoolpath = $temppath;
}
if (is_dir($spoolpath) AND is_writable($spoolpath)) {
set_config("system", "spoolpath", $spoolpath);
return($spoolpath);
}
}
return "";
}
function get_temppath() {
$a = get_app();

View file

@ -1,19 +0,0 @@
ALTER TABLE `profile` DROP INDEX `pub_keywords` ;
ALTER TABLE `profile` DROP INDEX `prv_keywords` ;
ALTER TABLE `item` DROP INDEX `title` ;
ALTER TABLE `item` DROP INDEX `body` ;
ALTER TABLE `item` DROP INDEX `allow_cid` ;
ALTER TABLE `item` DROP INDEX `allow_gid` ;
ALTER TABLE `item` DROP INDEX `deny_cid` ;
ALTER TABLE `item` DROP INDEX `deny_gid` ;
ALTER TABLE `item` DROP INDEX `tag` ;
ALTER TABLE `item` DROP INDEX `file` ;
SELECT CONCAT('ALTER TABLE ',table_schema,'.',table_name,' engine=InnoDB;')
FROM information_schema.tables
WHERE engine = 'MyISAM';

View file

@ -1,6 +1,6 @@
-- ------------------------------------------
-- Friendica 3.5-dev (Asparagus)
-- DB_UPDATE_VERSION 1200
-- Friendica 3.5.1-dev (Asparagus)
-- DB_UPDATE_VERSION 1208
-- ------------------------------------------
@ -58,8 +58,9 @@ CREATE TABLE IF NOT EXISTS `cache` (
`v` text,
`expire_mode` int(11) NOT NULL DEFAULT 0,
`updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY(`k`),
INDEX `updated` (`updated`)
PRIMARY KEY(`k`(191)),
INDEX `updated` (`updated`),
INDEX `expire_mode_updated` (`expire_mode`,`updated`)
) DEFAULT CHARSET=utf8mb4;
--
@ -97,7 +98,7 @@ CREATE TABLE IF NOT EXISTS `config` (
`k` varchar(255) NOT NULL DEFAULT '',
`v` text,
PRIMARY KEY(`id`),
INDEX `cat_k` (`cat`(30),`k`(30))
UNIQUE INDEX `cat_k` (`cat`(30),`k`(30))
) DEFAULT CHARSET=utf8mb4;
--
@ -118,6 +119,7 @@ CREATE TABLE IF NOT EXISTS `contact` (
`about` text,
`keywords` text,
`gender` varchar(32) NOT NULL DEFAULT '',
`xmpp` varchar(255) NOT NULL DEFAULT '',
`attag` varchar(255) NOT NULL DEFAULT '',
`avatar` varchar(255) NOT NULL DEFAULT '',
`photo` text,
@ -157,6 +159,7 @@ CREATE TABLE IF NOT EXISTS `contact` (
`writable` tinyint(1) NOT NULL DEFAULT 0,
`forum` tinyint(1) NOT NULL DEFAULT 0,
`prv` tinyint(1) NOT NULL DEFAULT 0,
`contact-type` int(11) unsigned NOT NULL DEFAULT 0,
`hidden` tinyint(1) NOT NULL DEFAULT 0,
`archive` tinyint(1) NOT NULL DEFAULT 0,
`pending` tinyint(1) NOT NULL DEFAULT 1,
@ -172,6 +175,7 @@ CREATE TABLE IF NOT EXISTS `contact` (
`ffi_keyword_blacklist` mediumtext,
PRIMARY KEY(`id`),
INDEX `uid` (`uid`),
INDEX `addr_uid` (`addr`,`uid`),
INDEX `nurl` (`nurl`)
) DEFAULT CHARSET=utf8mb4;
@ -199,7 +203,8 @@ CREATE TABLE IF NOT EXISTS `deliverq` (
`cmd` varchar(32) NOT NULL DEFAULT '',
`item` int(11) NOT NULL DEFAULT 0,
`contact` int(11) NOT NULL DEFAULT 0,
PRIMARY KEY(`id`)
PRIMARY KEY(`id`),
UNIQUE INDEX `cmd_item_contact` (`cmd`,`item`,`contact`)
) DEFAULT CHARSET=utf8mb4;
--
@ -326,6 +331,7 @@ CREATE TABLE IF NOT EXISTS `gcontact` (
`gender` varchar(32) NOT NULL DEFAULT '',
`birthday` varchar(32) NOT NULL DEFAULT '0000-00-00',
`community` tinyint(1) NOT NULL DEFAULT 0,
`contact-type` tinyint(1) NOT NULL DEFAULT -1,
`hide` tinyint(1) NOT NULL DEFAULT 0,
`nsfw` tinyint(1) NOT NULL DEFAULT 0,
`network` varchar(255) NOT NULL DEFAULT '',
@ -652,6 +658,8 @@ CREATE TABLE IF NOT EXISTS `notify` (
`seen` tinyint(1) NOT NULL DEFAULT 0,
`verb` varchar(255) NOT NULL DEFAULT '',
`otype` varchar(16) NOT NULL DEFAULT '',
`name_cache` tinytext,
`msg_cache` mediumtext,
PRIMARY KEY(`id`),
INDEX `uid` (`uid`)
) DEFAULT CHARSET=utf8mb4;
@ -677,7 +685,7 @@ CREATE TABLE IF NOT EXISTS `oembed` (
`url` varchar(255) NOT NULL,
`content` text,
`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY(`url`),
PRIMARY KEY(`url`(191)),
INDEX `created` (`created`)
) DEFAULT CHARSET=utf8mb4;
@ -690,7 +698,7 @@ CREATE TABLE IF NOT EXISTS `parsed_url` (
`oembed` tinyint(1) NOT NULL DEFAULT 0,
`content` text,
`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY(`url`,`guessing`,`oembed`),
PRIMARY KEY(`url`(191),`guessing`,`oembed`),
INDEX `created` (`created`)
) DEFAULT CHARSET=utf8mb4;
@ -704,7 +712,7 @@ CREATE TABLE IF NOT EXISTS `pconfig` (
`k` varchar(255) NOT NULL DEFAULT '',
`v` mediumtext,
PRIMARY KEY(`id`),
INDEX `uid_cat_k` (`uid`,`cat`(30),`k`(30))
UNIQUE INDEX `uid_cat_k` (`uid`,`cat`(30),`k`(30))
) DEFAULT CHARSET=utf8mb4;
--
@ -734,7 +742,9 @@ CREATE TABLE IF NOT EXISTS `photo` (
`deny_cid` mediumtext,
`deny_gid` mediumtext,
PRIMARY KEY(`id`),
INDEX `uid` (`uid`),
INDEX `uid_contactid` (`uid`,`contact-id`),
INDEX `uid_profile` (`uid`,`profile`),
INDEX `uid_album_created` (`uid`,`album`,`created`),
INDEX `resource-id` (`resource-id`),
INDEX `guid` (`guid`)
) DEFAULT CHARSET=utf8mb4;
@ -771,6 +781,17 @@ CREATE TABLE IF NOT EXISTS `poll_result` (
INDEX `choice` (`choice`)
) DEFAULT CHARSET=utf8mb4;
--
-- TABLE process
--
CREATE TABLE IF NOT EXISTS `process` (
`pid` int(10) unsigned NOT NULL,
`command` varchar(32) NOT NULL DEFAULT '',
`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY(`pid`),
INDEX `command` (`command`)
) DEFAULT CHARSET=utf8mb4;
--
-- TABLE profile
--
@ -812,6 +833,7 @@ CREATE TABLE IF NOT EXISTS `profile` (
`education` text,
`contact` text,
`homepage` varchar(255) NOT NULL DEFAULT '',
`xmpp` varchar(255) NOT NULL DEFAULT '',
`photo` varchar(255) NOT NULL DEFAULT '',
`thumb` varchar(255) NOT NULL DEFAULT '',
`publish` tinyint(1) NOT NULL DEFAULT 0,
@ -877,6 +899,7 @@ CREATE TABLE IF NOT EXISTS `register` (
`uid` int(11) unsigned NOT NULL DEFAULT 0,
`password` varchar(255) NOT NULL DEFAULT '',
`language` varchar(16) NOT NULL DEFAULT '',
`note` text,
PRIMARY KEY(`id`)
) DEFAULT CHARSET=utf8mb4;
@ -957,6 +980,7 @@ CREATE TABLE IF NOT EXISTS `term` (
INDEX `type_term` (`type`,`term`),
INDEX `uid_otype_type_term_global_created` (`uid`,`otype`,`type`,`term`,`global`,`created`),
INDEX `otype_type_term_tid` (`otype`,`type`,`term`,`tid`),
INDEX `uid_otype_type_url` (`uid`,`otype`,`type`,`url`),
INDEX `guid` (`guid`)
) DEFAULT CHARSET=utf8mb4;
@ -1048,6 +1072,7 @@ CREATE TABLE IF NOT EXISTS `user` (
`cntunkmail` int(11) NOT NULL DEFAULT 10,
`notify-flags` int(11) unsigned NOT NULL DEFAULT 65535,
`page-flags` int(11) unsigned NOT NULL DEFAULT 0,
`account-type` int(11) unsigned NOT NULL DEFAULT 0,
`prvnets` tinyint(1) NOT NULL DEFAULT 0,
`pwdreset` varchar(255) NOT NULL DEFAULT '',
`maxreq` int(11) NOT NULL DEFAULT 10,

View file

@ -1,6 +1,8 @@
Accesskeys in Friendica
=======================
* [Home](help)
General
-------
* p: profile

View file

@ -1,187 +1,556 @@
Friendica BBCode tags reference
========================
* [Home](help)
* [Creating posts](help/Text_editor)
Inline
-----
## Inline
<style>
table.bbcodes {
margin: 1em 0;
background-color: #f9f9f9;
border: 1px solid #aaa;
border-collapse: collapse;
color: #000;
width: 100%;
}
<pre>[b]bold[/b]</pre> : <strong>bold</strong>
table.bbcodes > tr > th,
table.bbcodes > tr > td,
table.bbcodes > * > tr > th,
table.bbcodes > * > tr > td {
border: 1px solid #aaa;
padding: 0.2em 0.4em
}
<pre>[i]italic[/i]</pre> : <em>italic</em>
table.bbcodes > tr > th,
table.bbcodes > * > tr > th {
background-color: #f2f2f2;
text-align: center;
width: 50%
}
</style>
<pre>[u]underlined[/u]</pre> : <u>underlined</u>
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Result</th>
</tr>
<tr>
<td>[b]bold[/b]</td>
<td><strong>bold</strong></td>
</tr>
<tr>
<td>[i]italic[/i]</td>
<td><em>italic</em></td>
</tr>
<tr>
<td>[u]underlined[/u]</td>
<td><u>underlined</u></td>
</tr>
<tr>
<td>[s]strike[/s]</td>
<td><strike>strike</strike></td>
</tr>
<tr>
<td>[o]overline[/o]</td>
<td><span class="overline">overline</span></td>
</tr>
<tr>
<td>[color=red]red[/color]</td>
<td><span style="color: red;">red</span></td>
</tr>
<tr>
<td>[url=http://www.friendica.com]Friendica[/url]</td>
<td><a href="http://www.friendica.com" target="external-link">Friendica</a></td>
</tr>
<tr>
<td>[img]http://friendica.com/sites/default/files/friendika-32.png[/img]</td>
<td><img src="http://friendica.com/sites/default/files/friendika-32.png" alt="Immagine/foto"></td>
</tr>
<tr>
<td>[img=64x32]http://friendica.com/sites/default/files/friendika-32.png[/img]<br>
<br>Note: provided height is simply discarded.</td>
<td><img src="http://friendica.com/sites/default/files/friendika-32.png" style="width: 64px;"></td>
</tr>
<tr>
<td>[size=xx-small]small text[/size]</td>
<td><span style="font-size: xx-small;">small text</span></td>
</tr>
<tr>
<td>[size=xx-large]big text[/size]</td>
<td><span style="font-size: xx-large;">big text</span></td>
</tr>
<tr>
<td>[size=20]exact size[/size] (size can be any number, in pixel)</td>
<td><span style="font-size: 20px;">exact size</span></td>
</tr>
<tr>
<td>[font=serif]Serif font[/font]</td>
<td><span style="font-family: serif;">Serif font</span></td>
</tr>
</table>
<pre>[s]strike[/s]</pre> : <strike>strike</strike>
### Links
<pre>[color=red]red[/color]</pre> : <span style="color: red;">red</span>
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Result</th>
</tr>
<tr>
<td>[url]http://friendica.com[/url]</td>
<td><a href="http://friendica.com">http://friendica.com</a></td>
</tr>
<tr>
<td>[url=http://friendica.com]Friendica[/url]</td>
<td><a href="http://friendica.com">Friendica</a></td>
</tr>
<tr>
<td>[bookmark]http://friendica.com[/bookmark]<br><br>
#^[url]http://friendica.com[/url]</td>
<td><span class="oembed link"><h4>Friendica: <a href="http://friendica.com" rel="oembed"></a><a href="http://friendica.com" target="_blank">http://friendica.com</a></h4></span></td>
</tr>
<tr>
<td>[bookmark=http://friendica.com]Bookmark[/bookmark]<br><br>
#^[url=http://friendica.com]Bookmark[/url]<br><br>
#[url=http://friendica.com]^[/url][url=http://friendica.com]Bookmark[/url]</td>
<td><span class="oembed link"><h4>Friendica: <a href="http://friendica.com" rel="oembed"></a><a href="http://friendica.com" target="_blank">Bookmark</a></h4></span></td>
</tr>
<tr>
<td>[url=/posts/f16d77b0630f0134740c0cc47a0ea02a]Diaspora post with GUID[/url]</td>
<td><a href="/display/f16d77b0630f0134740c0cc47a0ea02a" target="_blank">Diaspora post with GUID</a></td>
</tr>
<tr>
<td>#Friendica</td>
<td>#<a href="/search?tag=Friendica">Friendica</a></td>
</tr>
<tr>
<td>@Mention</td>
<td>@<a href="javascript:void(0)">Mention</a></td>
</tr>
<tr>
<td>acct:account@friendica.host.com (WebFinger)</td>
<td><a href="/acctlink?addr=account@friendica.host.com" target="extlink">acct:account@friendica.host.com</a></td>
</tr>
<tr>
<td>[mail]user@mail.example.com[/mail]</td>
<td><a href="mailto:user@mail.example.com">user@mail.example.com</a></td>
</tr>
<tr>
<td>[mail=user@mail.example.com]Send an email to User[/mail]</td>
<td><a href="mailto:user@mail.example.com">Send an email to User</a></td>
</tr>
</table>
<pre>[url=http://www.friendica.com]Friendica[/url]</pre> : <a href="http://www.friendica.com" target="external-link">Friendica</a>
## Blocks
<pre>[img]http://friendica.com/sites/default/files/friendika-32.png[/img]</pre> : <img src="http://friendica.com/sites/default/files/friendika-32.png" alt="Immagine/foto">
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Result</th>
</tr>
<tr>
<td>[p]A paragraph of text[/p]</td>
<td><p>A paragraph of text</p></td>
</tr>
<tr>
<td>Inline [code]code[/code] in a paragraph</td>
<td>Inline <key>code</key> in a paragraph</td>
</tr>
<tr>
<td>[code]Multi<br>line<br>code[/code]</td>
<td><code>Multi
line
code</code></td>
</tr>
<tr>
<td>[code=php]function text_highlight($s,$lang)[/code]</td>
<td><code><div class="hl-main"><ol class="hl-main"><li><span class="hl-code">&nbsp;</span><span class="hl-reserved">function</span><span class="hl-code"> </span><span class="hl-identifier">text_highlight</span><span class="hl-brackets">(</span><span class="hl-var">$s</span><span class="hl-code">,</span><span class="hl-var">$lang</span><span class="hl-brackets">)</span></li></ol></div></code></td>
</tr>
<tr>
<td>[quote]quote[/quote]</td>
<td><blockquote>quote</blockquote></td>
</tr>
<tr>
<td>[quote=Author]Author? Me? No, no, no...[/quote]</td>
<td><strong class="author">Author wrote:</strong><blockquote>Author? Me? No, no, no...</blockquote></td>
</tr>
<tr>
<td>[center]Centered text[/center]</td>
<td><div style="text-align:center;">Centered text</div></td>
</tr>
<tr>
<td>You should not read any further if you want to be surprised.[spoiler]There is a happy end.[/spoiler]</td>
<td>
<div class="wall-item-container">
You should not read any further if you want to be surprised.<br>
<span id="spoiler-wrap-0716e642" class="spoiler-wrap fakelink" onclick="openClose('spoiler-0716e642');">Click to open/close</span>
<blockquote class="spoiler" id="spoiler-0716e642" style="display: none;">There is a happy end.</blockquote>
<div class="body-attach"><div class="clear"></div></div>
</div>
</td>
</tr>
<tr>
<td>[spoiler=Author]Spoiler quote[/spoiler]</td>
<td>
<div class="wall-item-container">
<strong class="spoiler">Author wrote:</strong><br>
<span id="spoiler-wrap-a893765a" class="spoiler-wrap fakelink" onclick="openClose('spoiler-a893765a');">Click to open/close</span>
<blockquote class="spoiler" id="spoiler-a893765a" style="display: none;">Spoiler quote</blockquote>
<div class="body-attach"><div class="clear"></div></div>
</div>
</td>
</tr>
<tr>
<td>[hr] (horizontal line)</td>
<td><hr></td>
</tr>
</table>
<pre>[size=xx-small]small text[/size]</pre> : <span style="font-size: xx-small;">small text</span>
### Titles
<pre>[size=xx-large]big text[/size]</pre> : <span style="font-size: xx-large;">big text</span>
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Result</th>
</tr>
<tr>
<td>[h1]Title 1[/h1]</td>
<td><h1>Title 1</h1></td>
</tr>
<tr>
<td>[h2]Title 2[/h2]</td>
<td><h2>Title 2</h2></td>
</tr>
<tr>
<td>[h3]Title 3[/h3]</td>
<td><h3>Title 3</h3></td>
</tr>
<tr>
<td>[h4]Title 4[/h4]</td>
<td><h4>Title 4</h4></td>
</tr>
<tr>
<td>[h5]Title 5[/h5]</td>
<td><h5>Title 5</h5></td>
</tr>
<tr>
<td>[h6]Title 6[/h6]</td>
<td><h6>Title 6</h6></td>
</tr>
</table>
<pre>[size=20]exact size[/size] (size can be any number, in pixel)</pre> : <span style="font-size: 20px;">exact size</span>
### Tables
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Result</th>
</tr>
<tr>
<td>[table]<br>
&nbsp;&nbsp;[tr]<br>
&nbsp;&nbsp;&nbsp;&nbsp;[th]Header 1[/th]<br>
&nbsp;&nbsp;&nbsp;&nbsp;[th]Header 2[/th]<br>
&nbsp;&nbsp;&nbsp;&nbsp;[th]Header 2[/th]<br>
&nbsp;&nbsp;[/tr]<br>
&nbsp;&nbsp;[tr]<br>
&nbsp;&nbsp;&nbsp;&nbsp;[td]Cell 1[/td]<br>
&nbsp;&nbsp;&nbsp;&nbsp;[td]Cell 2[/td]<br>
&nbsp;&nbsp;&nbsp;&nbsp;[td]Cell 3[/td]<br>
&nbsp;&nbsp;[/tr]<br>
&nbsp;&nbsp;[tr]<br>
&nbsp;&nbsp;&nbsp;&nbsp;[td]Cell 4[/td]<br>
&nbsp;&nbsp;&nbsp;&nbsp;[td]Cell 5[/td]<br>
&nbsp;&nbsp;&nbsp;&nbsp;[td]Cell 6[/td]<br>
&nbsp;&nbsp;[/tr]<br>
[/table]</td>
<td>
<table>
<tbody>
<tr>
<th>Header 1</th>
<th>Header 2</th>
<th>Header 3</th>
</tr>
<tr>
<td>Cell 1</td>
<td>Cell 2</td>
<td>Cell 3</td>
</tr>
<tr>
<td>Cell 4</td>
<td>Cell 5</td>
<td>Cell 6</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td>[table border=0]</td>
<td>
<table border="0">
<tbody>
<tr>
<th>Header 1</th>
<th>Header 2</th>
<th>Header 3</th>
</tr>
<tr>
<td>Cell 1</td>
<td>Cell 2</td>
<td>Cell 3</td>
</tr>
<tr>
<td>Cell 4</td>
<td>Cell 5</td>
<td>Cell 6</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td>[table border=1]</td>
<td>
<table border="1">
<tbody>
<tr>
<th>Header 1</th>
<th>Header 2</th>
<th>Header 3</th>
</tr>
<tr>
<td>Cell 1</td>
<td>Cell 2</td>
<td>Cell 3</td>
</tr>
<tr>
<td>Cell 4</td>
<td>Cell 5</td>
<td>Cell 6</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
### Lists
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Result</th>
</tr>
<tr>
<td>[ul]<br>
&nbsp;&nbsp;[li] First list element<br>
&nbsp;&nbsp;[li] Second list element<br>
[/ul]<br>
[list]<br>
&nbsp;&nbsp;[*] First list element<br>
&nbsp;&nbsp;[*] Second list element<br>
[/list]</td>
<td>
<ul class="listbullet" style="list-style-type: circle;">
<li>First list element</li>
<li>Second list element</li>
</ul>
</td>
</tr>
<tr>
<td>[ol]<br>
&nbsp;&nbsp;[*] First list element<br>
&nbsp;&nbsp;[*] Second list element<br>
[/ol]<br>
[list=1]<br>
&nbsp;&nbsp;[*] First list element<br>
&nbsp;&nbsp;[*] Second list element<br>
[/list]</td>
<td>
<ul class="listdecimal" style="list-style-type: decimal;">
<li> First list element</li>
<li> Second list element</li>
</ul>
</td>
</tr>
<tr>
<td>[list=]<br>
&nbsp;&nbsp;[*] First list element<br>
&nbsp;&nbsp;[*] Second list element<br>
[/list]</td>
<td>
<ul class="listnone" style="list-style-type: none;">
<li> First list element</li>
<li> Second list element</li>
</ul>
</td>
</tr>
<tr>
<td>[list=i]<br>
&nbsp;&nbsp;[*] First list element<br>
&nbsp;&nbsp;[*] Second list element<br>
[/list]</td>
<td>
<ul class="listlowerroman" style="list-style-type: lower-roman;">
<li> First list element</li>
<li> Second list element</li>
</ul>
</td>
</tr>
<tr>
<td>[list=I]<br>
&nbsp;&nbsp;[*] First list element<br>
&nbsp;&nbsp;[*] Second list element<br>
[/list]</td>
<td>
<ul class="listupperroman" style="list-style-type: upper-roman;">
<li> First list element</li>
<li> Second list element</li>
</ul>
</td>
</tr>
<tr>
<td>[list=a]<br>
&nbsp;&nbsp;[*] First list element<br>
&nbsp;&nbsp;[*] Second list element<br>
[/list]</td>
<td>
<ul class="listloweralpha" style="list-style-type: lower-alpha;">
<li> First list element</li>
<li> Second list element</li>
</ul>
</td>
</tr>
<tr>
<td>[list=A]<br>
&nbsp;&nbsp;[*] First list element<br>
&nbsp;&nbsp;[*] Second list element<br>
[/list]</td>
<td>
<ul class="listupperalpha" style="list-style-type: upper-alpha;">
<li> First list element</li>
<li> Second list element</li>
</ul>
</td>
</tr>
</table>
Block
-----
<pre>[code]code[/code]</pre>
<code>code</code>
<p style="clear:both;">&nbsp;</p>
<pre>[code=php]function text_highlight($s,$lang)[/code]</pre>
<code><div class="hl-main"><ol class="hl-main"><li><span class="hl-code">&nbsp;</span><span class="hl-reserved">function</span><span class="hl-code"> </span><span class="hl-identifier">text_highlight</span><span class="hl-brackets">(</span><span class="hl-var">$s</span><span class="hl-code">,</span><span class="hl-var">$lang</span><span class="hl-brackets">)</span></li></ol></div></code>
<p style="clear:both;">&nbsp;</p>
<pre>[quote]quote[/quote]</pre>
<blockquote>quote</blockquote>
<p style="clear:both;">&nbsp;</p>
<pre>[quote=Author]Author? Me? No, no, no...[/quote]</pre>
<strong class="author">Author wrote:</strong><blockquote>Author? Me? No, no, no...</blockquote>
<p style="clear:both;">&nbsp;</p>
<pre>[center]centered text[/center]</pre>
<div style="text-align:center;">centered text</div>
<p style="clear:both;">&nbsp;</p>
<pre>You should not read any further if you want to be surprised.[spoiler]There is a happy end.[/spoiler]</pre>
You should not read any further if you want to be surprised.<br />*click to open/close*
(The text between thhe opening and the closing of the spoiler tag will be visible once the link is clicked. So *"There is a happy end."* wont be visible until the spoiler is uncovered.)
<p style="clear:both;">&nbsp;</p>
**Table**
<pre>[table border=1]
[tr]
[th]Tables now[/th]
[/tr]
[tr]
[td]Have headers[/td]
[/tr]
[/table]</pre>
<table border="1"><tbody><tr><th>Tables now</th></tr><tr><td>Have headers</td></tr></tbody></table>
<p style="clear:both;">&nbsp;</p>
**List**
<pre>[list]
[*] First list element
[*] Second list element
[/list]</pre>
<ul class="listbullet" style="list-style-type: circle;">
<li> First list element<br>
</li>
<li> Second list element</li>
</ul>
[list] is equivalent to [ul] (unordered list).
[ol] can be used instead of [list] to show an ordered list:
<pre>[ol]
[*] First list element
[*] Second list element
[/ol]</pre>
<ul class="listdecimal" style="list-style-type: decimal;"><li> First list element<br></li><li> Second list element</li></ul>
For more options on ordered lists, you can define the style of numeration on [list] argument:
<pre>[list=1]</pre> : decimal
<pre>[list=i]</pre> : lover case roman
<pre>[list=I]</pre> : upper case roman
<pre>[list=a]</pre> : lover case alphabetic
<pre>[list=A] </pre> : upper case alphabetic
Embed
------
## Embed
You can embed video, audio and more in a message.
<pre>[video]url[/video]</pre>
<pre>[audio]url[/audio]</pre>
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Result</th>
</tr>
<tr>
<td>[video]url[/video]</td>
<td>Where *url* can be an url to youtube, vimeo, soundcloud, or other sites wich supports oembed or opengraph specifications.</td>
</tr>
<tr>
<td>[video]Video file url[/video]
[audio]Audio file url[/audio]</td>
<td>Full URL to an ogg/ogv/oga/ogm/webm/mp4/mp3 file. An HTML5 player will be used to show it.</td>
</tr>
<tr>
<td>[youtube]Youtube URL[/youtube]</td>
<td>Youtube video OEmbed display. May not embed an actual player.</td>
</tr>
<tr>
<td>[youtube]Youtube video ID[/youtube]</td>
<td>Youtube player iframe embed.</td>
</tr>
<tr>
<td>[vimeo]Vimeo URL[/vimeo]</td>
<td>Vimeo video OEmbed display. May not embed an actual player.</td>
</tr>
<tr>
<td>[vimeo]Vimeo video ID[/vimeo]</td>
<td>Vimeo player iframe embed.</td>
</tr>
<tr>
<td>[embed]URL[/embed]</td>
<td>Embed OEmbed rich content.</td>
</tr>
<tr>
<td>[iframe]URL[/iframe]</td>
<td>General embed, iframe size is limited by the theme size for video players.</td>
</tr>
<tr>
<td>[url]*url*[/url]</td>
<td>If *url* supports oembed or opengraph specifications the embedded object will be shown (eg, documents from scribd).
Page title with a link to *url* will be shown.</td>
</tr>
</table>
Where *url* can be an url to youtube, vimeo, soundcloud, or other sites wich supports oembed or opengraph specifications.
*url* can be also full url to an ogg file. HTML5 tag will be used to show it.
## Map
<pre>[url]*url*[/url]</pre>
This require "openstreetmap" or "Google Maps" addon version 1.3 or newer.
If the addon isn't activated, the raw coordinates are shown instead.
If *url* supports oembed or opengraph specifications the embedded object will be shown (eg, documents from scribd).
Page title with a link to *url* will be shown.
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Result</th>
</tr>
<tr>
<td>[map]address[/map]</td>
<td>Embeds a map centered on this address.</td>
</tr>
<tr>
<td>[map=lat,long]</td>
<td>Embeds a map centered on those coordinates.</td>
</tr>
<tr>
<td>[map]</td>
<td>Embeds a map centered on the post's location.</td>
</tr>
</table>
Map
---
## Abstract for longer posts
<pre>[map]address[/map]</pre>
<pre>[map=lat,long]</pre>
You can embed maps from coordinates or addresses.
This require "openstreetmap" addon version 1.3 or newer.
-----------------------------------------------------------
Abstract for longer posts
-------------------------
If you want to spread your post to several third party networks you can have the problem that these networks have (for example) a length limitation.
(Like on Twitter)
If you want to spread your post to several third party networks you can have the problem that these networks have a length limitation like on Twitter.
Friendica is using a semi intelligent mechanism to generate a fitting abstract.
But it can be interesting to define an own abstract that will only be displayed on the external network.
But it can be interesting to define a custom abstract that will only be displayed on the external network.
This is done with the [abstract]-element.
Example:
<pre>[abstract]Totally interesting! A must-see! Please click the link![/abstract]
I want to tell you a really boring story that you really never wanted
to hear.</pre>
Twitter would display the text "Totally interesting! A must-see! Please click the link!".
On Friendica you would only see the text after "I want to tell you a really ..."
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Result</th>
</tr>
<tr>
<td>[abstract]Totally interesting! A must-see! Please click the link![/abstract]<br>
I want to tell you a really boring story that you really never wanted to hear.</td>
<td>Twitter would display the text <blockquote>Totally interesting! A must-see! Please click the link!</blockquote>
On Friendica you would only see the text after <blockquote>I want to tell you a really ...</blockquote></td>
</tr>
</table>
It is even possible to define abstracts for separate networks:
<pre>
[abstract]Hi friends Here are my newest pictures![abstract]
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Result</th>
</tr>
<tr>
<td>
[abstract]Hi friends Here are my newest pictures![/abstract]<br>
[abstract=twit]Hi my dear Twitter followers. Do you want to see my new
pictures?[abstract]
pictures?[/abstract]<br>
[abstract=apdn]Helly my dear followers on ADN. I made sone new pictures
that I wanted to share with you.[abstract]
Today I was in the woods and took some real cool pictures ...
</pre>
For Twitter and App.net the system will use the defined abstracts.
For other networks (e.g. when you are using the "statusnet" connector that is used to post to GNU Social) the general abstract element will be used.
that I wanted to share with you.[/abstract]<br>
Today I was in the woods and took some real cool pictures ...</td>
<td>For Twitter and App.net the system will use the defined abstracts.<br>
For other networks (e.g. when you are using the "statusnet" connector that is used to post to your GNU Social account) the general abstract element will be used.</td>
</tr>
</table>
If you use (for example) the "buffer" connector to post to Facebook or Google+ you can use this element to define an abstract for a longer blogpost that you don't want to post completely to these networks.
@ -189,20 +558,59 @@ Networks like Facebook or Google+ aren't length limited.
For this reason the [abstract] element isn't used.
Instead you have to name the explicit network:
<pre>
[abstract]These days I had a strange encounter ...[abstract]
[abstract=goog]Helly my dear Google+ followers. You have to read my
newest blog post![abstract]
[abstract=face]Hello my Facebook friends. These days happened something
really cool.[abstract]
While taking pictures in the woods I had a really strange encounter ... </pre>
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Result</th>
</tr>
<tr>
<td>
[abstract]These days I had a strange encounter...[/abstract]<br>
[abstract=goog]Helly my dear Google+ followers. You have to read my newest blog post![/abstract]<br>
[abstract=face]Hello my Facebook friends. These days happened something really cool.[/abstract]<br>
While taking pictures in the woods I had a really strange encounter...</td>
<td>Google and Facebook will show the respective abstracts while the other networks will show the default one.<br>
<br>Meanwhile, Friendica won't show any of the abstracts.</td>
</tr>
</table>
The [abstract] element isn't working with the native OStatus connection or with connectors where we post the HTML.
(Like Tumblr, Wordpress or Pump.io)
The [abstract] element isn't working with connectors where we post the HTML like Tumblr, Wordpress or Pump.io.
For the native connections--that is to e.g. Friendica, Hubzilla, Diaspora or GNU Social--the full posting is used and the contacts instance will display the posting as desired.
Special
-------
## Special
If you need to put literal bbcode in a message, [noparse], [nobb] or [pre] are used to escape bbcode:
<pre>[noparse][b]bold[/b][/noparse]</pre> : [b]bold[/b]
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Result</th>
</tr>
<tr>
<td>If you need to put literal bbcode in a message, [noparse], [nobb] or [pre] are used to escape bbcode:
<ul>
<li>[noparse][b]bold[/b][/noparse]</li>
<li>[nobb][b]bold[/b][/nobb]</li>
<li>[pre][b]bold[/b][/pre]</li>
</ul>
</td>
<td>[b]bold[/b]</td>
</tr>
<tr>
<td>[nosmile] is used to disable smilies on a post by post basis<br>
<br>
[nosmile] ;-) :-O
</td>
<td>;-) :-O</td>
</tr>
<tr>
<td>Custom inline styles<br>
<br>
[style=text-shadow: 0 0 4px #CC0000;]You can change all the CSS properties of this block.[/style]</td>
<td><span style="text-shadow: 0 0 4px #cc0000;;">You can change all the CSS properties of this block.</span></td>
</tr>
<tr>
<td>Custom class block<br>
<br>
[class=custom]If the class exists, this block will have the custom class style applied.[/class]</td>
<td><pre>&lt;span class="custom"&gt;If the class exists,<br> this block will have the custom class<br> style applied.&lt;/span&gt;</pre></td>
</tr>
</table>

View file

@ -9,7 +9,7 @@ This helps us get new features faster.
You can also contact the [friendica support forum](https://helpers.pyxis.uberspace.de/profile/helpers) and report your problem there.
Maybe someone from another node encountered the problem as well and can help you.
If you're a technical user, or your site doesn't have a support page, you'll need to use the [Bug Tracker](http://bugs.friendica.com/).
If you're a technical user, or your site doesn't have a support page, you'll need to use the [Bug Tracker](https://github.com/friendica/friendica/issues).
Please perform a search to see if there's already an open bug that matches yours before submitting anything.
Try to provide as much information as you can about the bug, including the **full** text of any error messages or notices, and any steps required to replicate the problem in as much detail as possible.

View file

@ -7,11 +7,11 @@ Friendica Documentation and Resources
* [Account Basics](help/Account-Basics)
* [New User Quick Start](help/Quick-Start-guide)
* [Creating posts](help/Text_editor)
* [BBCode tag reference](help/BBCode)
* [BBCode tag reference](help/BBCode)
* [Comment, sort and delete posts](help/Text_comment)
* [Profiles](help/Profiles)
* [Accesskey reference](help/Accesskeys)
* [Events](help/events)
* [Events](help/events)
* You and other users
* [Connectors](help/Connectors)
* [Making Friends](help/Making-Friends)
@ -20,7 +20,6 @@ Friendica Documentation and Resources
* [Community Forums](help/Forums)
* [Chats](help/Chats)
* Further information
* [Improve Performance](help/Improve-Performance)
* [Move your account](help/Move-Account)
* [Delete your account](help/Remove-Account)
* [Frequently asked questions (FAQ)](help/FAQ)
@ -31,10 +30,9 @@ Friendica Documentation and Resources
* [Settings & Admin Panel](help/Settings)
* [Installing Connectors (Twitter/GNU Social)](help/Installing-Connectors)
* [Install an ejabberd server (XMPP chat) with synchronized credentials](help/install-ejabberd)
* [Message Flow](help/Message-Flow)
* [Using SSL with Friendica](help/SSL)
* [Twitter/GNU Social API Functions](help/api)
* [Config values that can only be set in .htconfig.php](help/htconfig)
* [Improve Performance](help/Improve-Performance)
**Developer Manual**
@ -46,9 +44,11 @@ Friendica Documentation and Resources
* [Plugin Development](help/Plugins)
* [Theme Development](help/themes)
* [Smarty 3 Templates](help/smarty3-templates)
* [Protocol Documentation](help/Protocol)
* [Database schema documantation](help/database)
* [Class Autoloading](help/autoloader)
* [Code - Reference(Doxygen generated - sets cookies)](doc/html/)
* [Twitter/GNU Social API Functions](help/api)
**External Resources**

View file

@ -4,7 +4,7 @@ Friendica Message Flow
This page documents some of the details of how messages get from one person to another in the Friendica network.
There are multiple paths, using multiple protocols and message formats.
Those attempting to understand these message flows should become familiar with (at the minimum) the [DFRN protocol document](http://dfrn.org/dfrn.pdf) and the message passing elements of the OStatus stack (salmon and Pubsubhubbub).
Those attempting to understand these message flows should become familiar with (at the minimum) the [DFRN protocol document](https://github.com/friendica/friendica/blob/master/spec/dfrn2.pdf) and the message passing elements of the OStatus stack (salmon and Pubsubhubbub).
Most message passing involves the file include/items.php, which has functions for several feed-related import/export activities.
@ -21,8 +21,8 @@ Push (pubsubhubbub) feeds arrive via mod/pubsub.php
DFRN-poll feed imports arrive via include/poller.php as a scheduled task, this implements the local side of the DFRN-poll protocol.
Scenario #1. Bob posts a public status message
---
### Scenario #1. Bob posts a public status message
This is a public message with no conversation members so no private transport is used.
There are two paths it can take - as a bbcode path to DFRN clients, and converted to HTML with the server's PuSH (pubsubhubbub) hubs notified.
When a PuSH hub is operational, dfrn-poll clients prefer to receive their information through the PuSH channel.
@ -30,31 +30,31 @@ They will fall back on a daily poll in case the hub has delivery issues (this is
If there is no specified hub or hubs, DFRN clients will poll at a configurable (per-contact) rate at up to 5-minute intervals.
Feeds retrieved via dfrn-poll are bbcode and may also contain private conversations which the poller has permissions to see.
Scenario #2. Jack replies to Bob's public message. Jack is on the Friendica/DFRN network.
---
### Scenario #2. Jack replies to Bob's public message. Jack is on the Friendica/DFRN network.
Jack uses dfrn-notify to send a direct reply to Bob.
Bob then creates a feed of the conversation and sends it to everybody involved in the conversation using dfrn-notify.
PuSH hubs are notified that new content is available.
The hub or hubs will then retrieve the latest feed and transmit it to all hub subscribers (which may be on different networks).
Scenario #3. Mary replies to Bob's public message. Mary is on the Friendica/DFRN network.
---
### Scenario #3. Mary replies to Bob's public message. Mary is on the Friendica/DFRN network.
Mary uses dfrn-notify to send a direct reply to Bob.
Bob then creates a feed of the conversation and sends it to everybody involved in the conversation (excluding himself, the conversation is now sent to both Jack and Mary).
Messages are sent using dfrn-notify.
Push hubs are also notified that new content is available.
The hub or hubs will then retrieve the latest feed and transmit it to all hub subscribers (which may be on different networks).
Scenario #4. William replies to Bob's public message. William is on the OStatus network.
---
### Scenario #4. William replies to Bob's public message. William is on the OStatus network.
William uses salmon to notify Bob of the reply.
Content is html embedded in salmon magic envelope.
Bob then creates a feed of the conversation and sends it to all Friendica participants involved in the conversation using dfrn-notify (excluding himself, the conversation is sent to both Jack and Mary).
Push hubs are notified that new content is available.
The hub or hubs will then retrieve the latest feed and transmit it to all hub subscribers (which may be on different networks).
Scenario #5. Bob posts a private message to Mary and Jack.
---
### Scenario #5. Bob posts a private message to Mary and Jack.
Message is delivered immediately to Mary and Jack using dfrn_notify.
Public hubs are not notified.
Requeueing is attempted in case of timeout.

42
doc/Protocol.md Normal file
View file

@ -0,0 +1,42 @@
Used Protocols
===============
* [Home](help)
Friendicas DFRN Protocol
---
* [Document with the DFRN specification](spec/dfrn2.pdf)
* [Schema of the contact request process](spec/dfrn2_contact_request.png)
* [Schema of the contact request confirmation](spec/dfrn2_contact_confirmation.png)
* [Description of the message flow](help/Message-Flow)
ActivityStreams
---
Friendica is using ActivityStreams in version 1.0 for its activities and object types.
Additional types are used for non standard activities.
* [Link to the specification](http://activitystrea.ms/head/activity-schema.html)
* [List of used ActivityStreams verbs and object types.](https://github.com/friendica/friendica/wiki/ActivityStreams)
Salmon
---
Salmon is used as a message exchange protocol for replies and mentions.
* [Link to the protocol summary](http://www.salmon-protocol.org/salmon-protocol-summary)
Portable Contacts
---
Portable Contacts is used for friends lists.
* [Link to the specification](https://web.archive.org/web/20160426223008/http://portablecontacts.net/draft-spec.html) (Link to archive.org)
pubsubhubbub
---
pubsubhubbub is used for OStatus.
* [Link to the specification](https://pubsubhubbub.github.io/PubSubHubbub/pubsubhubbub-core-0.4.html)

View file

@ -5,7 +5,7 @@ Using SSL with Friendica
Disclaimer
---
**This document has been updated in November 2015.
**This document has been updated in November 2016.
SSL encryption is relevant for security.
This means that recommended settings change fast.
Keep your setup up to date and do not rely on this document being updated as fast as technologies change!**
@ -40,65 +40,26 @@ If your Friendica instance is running on a shared hosting platform, you should f
They have instructions for you on how to do it there.
You can always order a paid certificate with your provider.
They will either install it for you or provide an easy way to upload the certificate and the key via a web interface.
It might be worth asking if your provider would install a certificate you provide yourself, to save money.
If so, read on.
Getting a free StartSSL certificate
---
StartSSL is a certificate authority that issues certificates for free.
They are valid for a year and are sufficient for our purposes.
### Step 1: Create a client certificate
When you initially sign up with StartSSL, you receive a certificate that is installed in your browser.
You need it for the login on startssl.com, also when coming back to the site later.
It has nothing to do with the SSL certificate for your server.
### Step 2: Validate your email address and your domain
To continue you have to prove that you own the email address you specified and the domain that you want a certificate for.
Specify your email address, request a validation link via email from the "validations wizard".
Same procedure for the domain validation.
### Step 3: Request the certificate
Go to the "certificates wizard".
Choose the target web server.
When you are first prompted for a domain to certify, you need to enter your main domain, e.g. example.com.
In the next step, you will be able to specify a subdomain for Friendica, if needed.
Example: If you have friendica.example.com, you first enter example.com, then specify the subdomain friendica later.
If you know how to generate an openssl key and a certificate signing request (csr) yourself, do so.
Paste the csr into your browser to get it signed by StartSSL.
If you do not know how to generate a key and a csr, accept StartSSL's offer to generate it for you.
This means: StartSSL has the key to your encryption but it is better than no certificate at all.
Download your certificate from the website.
(Or in the second case: Download your certificate and your key.)
To install your certificate on a server, you need one or two extra files: sub.class1.server.ca.pem and ca.pem, delivered by startssl.com
Go to the "Tool box" section and download "Class 1 Intermediate Server CA" and "StartCom Root CA (PEM encoded)".
If you want to send your certificate to your hosting provider, they need the certificate, the key and probably at least the intermediate server CA.
To be sure, send those three and the ca.pem file.
With some providers, you have to send them your certificate.
They need the certificate, the key and the CA's intermediate certificate.
To be sure, send those three files.
**You should send them to your provider via an encrypted channel!**
If you run your own server, upload the files and check out the Mozilla wiki link below.
Let's encrypt
Own server
---
If you run your own server, the "Let's encrypt" initiative might become an interesting alternative.
Their offer is in public beta right now.
Check out [their website](https://letsencrypt.org/) for status updates.
If you run your own server, we recommend to check out the ["Let's Encrypt" initiative](https://letsencrypt.org/).
Not only do they offer free SSL certificates, but also a way to automate their renewal.
You need to install a client software on your server to use it.
Instructions for the official client are [here](https://certbot.eff.org/).
Depending on your needs, you might want to look at the [list of alternative letsencrypt clients](https://letsencrypt.org/docs/client-options/).
Web server settings
---
Visit the [Mozilla's wiki](https://wiki.mozilla.org/Security/Server_Side_TLS) for instructions on how to configure a secure webserver.
They provide recommendations for [different web servers](https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended_Server_Configurations).
They provide recommendations for [different web servers](https://mozilla.github.io/server-side-tls/ssl-config-generator/).
Test your SSL settings
---

View file

@ -1,5 +1,7 @@
# Settings
* [Home](help)
If you are the admin of a Friendica node, you have access to the so called **Admin Panel** where you can configure your Friendica node.
On the front page of the admin panel you will see a summary of information about your node.
@ -9,7 +11,8 @@ This number should decrease quickly.
The second is the messages which could for various reasons not being delivered.
They will be resend later.
You can have a quick glance into that second queus in the "Inspect Queue" section of the admin panel.
If you have activated the background workers, there might be a third number representing the count of jobs queued for the workers.
If you have activated the background workers, there is a third number representing the count of jobs queued for the workers.
These worker tasks are prioritised and are done accordingly.
Then you get an overview of the accounts on your node, which can be moderated in the "Users" section of the panel.
As well as an overview of the currently active addons

View file

@ -8,7 +8,11 @@ Getting started
[Vagrant](https://www.vagrantup.com/) is a virtualization solution for developers.
No need to setup up a webserver, database etc. before actually starting.
Vagrant creates a virtual machine (an Ubuntu 14.04) for you that you can just run inside VirtualBox and start to work directly on Friendica.
Vagrant creates a virtual machine for you that you can just run inside VirtualBox and start to work directly on Friendica.
You can choose between two different Ubuntu Linux versions:
1. Ubuntu Trusty (14.04) with PHP 5.5.9 and MySQL 5.5.53
2. Ubuntu Xenial (16.04) with PHP 7.0 and MySQL 5.7.16
What you need to do:
@ -16,21 +20,27 @@ What you need to do:
Please use an up-to-date vagrant version from https://www.vagrantup.com/downloads.html.
2. Git clone your Friendica repository.
Inside, you'll find a "Vagrantfile" and some scripts in the utils folder.
3. Run "vagrant up" from inside the friendica clone.
3. Choose the Ubuntu version you'll need und run "vagrant up <ubuntu-version>" from inside the friendica clone:
$> vagrant up trusty
$> vagrant up xenial
Be patient: When it runs for the first time, it downloads an Ubuntu Server image.
4. Run "vagrant ssh" to log into the virtual machine to log in to the VM.
5. Open 192.168.22.10 in a browser.
4. Run "vagrant ssh <ubuntu-version>" to log into the virtual machine to log in to the VM:
$> vagrant ssh trusty
$> vagrant ssh xenial
5. Open you test installation in a browser.
If you selected an Ubuntu Trusty go to 192.168.22.10.
If you started a Xenial machine go to 192.168.22.11.
The mysql database is called "friendica", the mysql user and password both are "root".
6. Work on Friendica's code in your git clone on your machine (not in the VM).
Your local working directory is set up as a shared directory with the VM (/vagrant).
7. Check the changes in your browser in the VM.
Debug via the "vagrant ssh" login.
Debug via the "vagrant ssh <ubuntu-version>" login.
Find the Friendica log file /vagrant/logfile.out.
8. Commit and push your changes directly back to Github.
If you want to stop vagrant after finishing your work, run the following command
$> vagrant halt
$> vagrant halt <ubuntu-version>
in the development directory.
@ -44,10 +54,3 @@ You will then have the following accounts to login:
* friendica2 and friendica3 are conntected. friendica4 and friendica5 are connected.
For further documentation of vagrant, please see [the vagrant*docs*](https://docs.vagrantup.com/v2/).
**Important notice:**
If you already had an Ubuntu 12.04 Vagrant VM, please run
$> vagrant destroy
before starting the new 14.04 machine.

View file

@ -1,5 +1,8 @@
Friendica API
===
* [Home](help)
The Friendica API aims to be compatible to the [GNU Social API](http://wiki.gnusocial.de/gnusocial:api) and the [Twitter API](https://dev.twitter.com/rest/public).
Please refer to the linked documentation for further information.
@ -744,6 +747,38 @@ On success:
On error:
* different JSON returns {"result":"error","message":"searchstring not specified"}
---
### friendica/profile/show (GET; AUTH)
show data of all profiles or a single profile of the authenticated user
#### Parameters
* profile_id: id of the profile to be returned (optional, if omitted all profiles are returned by default)
#### Return values
On success: Array of:
* multi_profiles: true if user has activated multi_profiles
* global_dir: URL of the global directory set in server settings
* friendica_owner: user data of the authenticated user
* profiles: array of the profile data
On error:
HTTP 403 Forbidden: when no authentication provided
HTTP 400 Bad Request: if given profile_id is not in db or not assigned to authenticated user
General description of profile data in API returns:
* profile_id
* profile_name
* is_default: true if this is the public profile
* hide_friends: true if friends are hidden
* profile_photo
* profile_thumb
* publish: true if published on the server's local directory
* net_publish: true if published to global_dir
* description ... homepage: different data fields from 'profile' table in database
* users: array with the users allowed to view this profile (empty if is_default=true)
---
## Not Implemented API calls
The following API calls are implemented in GNU Social but not in Friendica: (incomplete)

View file

@ -1,22 +1,24 @@
Table notify
============
| Field | Description | Type | Null | Key | Default | Extra |
| ------ | --------------------------------- | ------------ | ---- | --- | ------------------- | --------------- |
| id | sequential ID | int(11) | NO | PRI | NULL | auto_increment |
| hash | | varchar(64) | NO | | | |
| type | | int(11) | NO | | 0 | |
| name | | varchar(255) | NO | | | |
| url | | varchar(255) | NO | | | |
| photo | | varchar(255) | NO | | | |
| date | | datetime | NO | | 0000-00-00 00:00:00 | |
| msg | | mediumtext | NO | | NULL | |
| uid | user.id of the owner of this data | int(11) | NO | MUL | 0 | |
| link | | varchar(255) | NO | | | |
| parent | | int(11) | NO | | 0 | |
| seen | | tinyint(1) | NO | | 0 | |
| verb | | varchar(255) | NO | | | |
| otype | | varchar(16) | NO | | | |
| iid | item.id | int(11) | NO | | 0 | |
| Field | Description | Type | Null | Key | Default | Extra |
| ---------- | --------------------------------- | ------------ | ---- | --- | ------------------- | --------------- |
| id | sequential ID | int(11) | NO | PRI | NULL | auto_increment |
| hash | | varchar(64) | NO | | | |
| type | | int(11) | NO | | 0 | |
| name | | varchar(255) | NO | | | |
| url | | varchar(255) | NO | | | |
| photo | | varchar(255) | NO | | | |
| date | | datetime | NO | | 0000-00-00 00:00:00 | |
| msg | | mediumtext | YES | | NULL | |
| uid | user.id of the owner of this data | int(11) | NO | MUL | 0 | |
| link | | varchar(255) | NO | | | |
| iid | item.id | int(11) | NO | | 0 | |
| parent | | int(11) | NO | | 0 | |
| seen | | tinyint(1) | NO | | 0 | |
| verb | | varchar(255) | NO | | | |
| otype | | varchar(16) | NO | | | |
| name_cache | Cached bbcode parsing of name | tinytext | YES | | NULL | |
| msg_cache | Cached bbcode parsing of msg | mediumtext | YES | | NULL | |
Return to [database documentation](help/database)

View file

@ -3,201 +3,616 @@ Referenz der Friendica BBCode Tags
* [Zur Startseite der Hilfe](help)
Inline Tags
-----
## Inline
<style>
table.bbcodes {
margin: 1em 0;
background-color: #f9f9f9;
border: 1px solid #aaa;
border-collapse: collapse;
color: #000;
width: 100%;
}
table.bbcodes > tr > th,
table.bbcodes > tr > td,
table.bbcodes > * > tr > th,
table.bbcodes > * > tr > td {
border: 1px solid #aaa;
padding: 0.2em 0.4em
}
table.bbcodes > tr > th,
table.bbcodes > * > tr > th {
background-color: #f2f2f2;
text-align: center;
width: 50%
}
</style>
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Ergebnis</th>
</tr>
<tr>
<td>[b]fett[/b]</td>
<td><strong>fett</strong></td>
</tr>
<tr>
<td>[i]kursiv[/i]</td>
<td><em>kursiv</em></td>
</tr>
<tr>
<td>[u]unterstrichen[/u]</td>
<td><u>unterstrichen</u></td>
</tr>
<tr>
<td>[s]durchgestrichen[/s]</td>
<td><strike>durchgestrichen</strike></td>
</tr>
<tr>
<td>[o]&uuml;berstrichen[/o]</td>
<td><span class="overline">&uuml;berstrichen</span></td>
</tr>
<tr>
<td>[color=red]rot[/color]</td>
<td><span style="color: red;">rot</span></td>
</tr>
<tr>
<td>[url=http://www.friendica.com]Friendica[/url]</td>
<td><a href="http://www.friendica.com" target="external-link">Friendica</a></td>
</tr>
<tr>
<td>[img]http://friendica.com/sites/default/files/friendika-32.png[/img]</td>
<td><img src="http://friendica.com/sites/default/files/friendika-32.png" alt="Immagine/foto"></td>
</tr>
<tr>
<td>[img=64x32]http://friendica.com/sites/default/files/friendika-32.png[/img]<br>
<br>Note: provided height is simply discarded.</td>
<td><img src="http://friendica.com/sites/default/files/friendika-32.png" style="width: 64px;"></td>
</tr>
<tr>
<td>[size=xx-small]kleiner Text[/size]</td>
<td><span style="font-size: xx-small;">kleiner Text</span></td>
</tr>
<tr>
<td>[size=xx-large]gro&szlig;er Text[/size]</td>
<td><span style="font-size: xx-large;">gro&szlig;er Text</span></td>
</tr>
<tr>
<td>[size=20]exakte Gr&ouml;&szlig;e[/size] (die Gr&ouml;&szlig;e kann beliebig in Pixeln gew&auml;lt werden)</td>
<td><span style="font-size: 20px;">exakte Gr&ouml;&szlig;e</span></td>
</tr>
<tr>
<td>[font=serif]Serife Schriftart[/font]</td>
<td><span style="font-family: serif;">Serife Schriftart</span></td>
</tr>
</table>
### Links
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Ergebnis</th>
</tr>
<tr>
<td>[url]http://friendica.com[/url]</td>
<td><a href="http://friendica.com">http://friendica.com</a></td>
</tr>
<tr>
<td>[url=http://friendica.com]Friendica[/url]</td>
<td><a href="http://friendica.com">Friendica</a></td>
</tr>
<tr>
<td>[bookmark]http://friendica.com[/bookmark]<br><br>
#^[url]http://friendica.com[/url]</td>
<td><span class="oembed link"><h4>Friendica: <a href="http://friendica.com" rel="oembed"></a><a href="http://friendica.com" target="_blank">http://friendica.com</a></h4></span></td>
</tr>
<tr>
<td>[bookmark=http://friendica.com]Lesezeichen[/bookmark]<br><br>
#^[url=http://friendica.com]Lesezeichen[/url]<br><br>
#[url=http://friendica.com]^[/url][url=http://friendica.com]Lesezeichen[/url]</td>
<td><span class="oembed link"><h4>Friendica: <a href="http://friendica.com" rel="oembed"></a><a href="http://friendica.com" target="_blank">Lesezeichen</a></h4></span></td>
</tr>
<tr>
<td>[url=/posts/f16d77b0630f0134740c0cc47a0ea02a]Diaspora Beitrag mit GUID[/url]</td>
<td><a href="/display/f16d77b0630f0134740c0cc47a0ea02a" target="_blank">Diaspora Beitrag mit GUID</a></td>
</tr>
<tr>
<td>#Friendica</td>
<td>#<a href="/search?tag=Friendica">Friendica</a></td>
</tr>
<tr>
<td>@Erw&auml;hnung</td>
<td>@<a href="javascript:void(0)">Erw&auml;hnung</a></td>
</tr>
<tr>
<td>acct:account@friendica.host.com (WebFinger)</td>
<td><a href="/acctlink?addr=account@friendica.host.com" target="extlink">acct:account@friendica.host.com</a></td>
</tr>
<tr>
<td>[mail]user@mail.example.com[/mail]</td>
<td><a href="mailto:user@mail.example.com">user@mail.example.com</a></td>
</tr>
<tr>
<td>[mail=user@mail.example.com]Eine E-Mail senden[/mail]</td>
<td><a href="mailto:user@mail.example.com">Eine E-Mail senden</a></td>
</tr>
</table>
## Blocks
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Ergebnis</th>
</tr>
<tr>
<td>[p]Ein Absatz mit Text[/p]</td>
<td><p>Ein Absatz mit Text</p></td>
</tr>
<tr>
<td>Eingebetteter [code]Programmcode[/code] im Text</td>
<td>Eingebetteter <key>Programmcode</key> im Text</td>
</tr>
<tr>
<td>[code]Programmcode<br>&uuml;ber<br>mehrere<br>Zeilen[/code]</td>
<td><code>Programmcode
&uuml;ber
mehrere
Zeilen</code></td>
</tr>
<tr>
<td>[code=php]function text_highlight($s,$lang)[/code]</td>
<td><code><div class="hl-main"><ol class="hl-main"><li><span class="hl-code">&nbsp;</span><span class="hl-reserved">function</span><span class="hl-code"> </span><span class="hl-identifier">text_highlight</span><span class="hl-brackets">(</span><span class="hl-var">$s</span><span class="hl-code">,</span><span class="hl-var">$lang</span><span class="hl-brackets">)</span></li></ol></div></code></td>
</tr>
<tr>
<td>[quote]Zitat[/quote]</td>
<td><blockquote>Zitat</blockquote></td>
</tr>
<tr>
<td>[quote=Autor]Autor? Ich? Nein, niemals...[/quote]</td>
<td><strong class="Autor">Autor hat geschrieben:</strong><blockquote>Autor? Ich? Nein, niemals...</blockquote></td>
</tr>
<tr>
<td>[center]zentrierter Text[/center]</td>
<td><div style="text-align:center;">zentrierter Text</div></td>
</tr>
<tr>
<td>Du solltest nicht weiter lesen, wenn du das Ende des Films nicht vorher erfahren willst. [spoiler]Es gibt ein Happy End.[/spoiler]</td>
<td>
<div class="wall-item-container">
Du solltest nicht weiter lesen, wenn du das Ende des Films nicht vorher erfahren willst. <br>
<span id="spoiler-wrap-0716e642" class="spoiler-wrap fakelink" onclick="openClose('spoiler-0716e642');">Zum &ouml;ffnen/schlie&szlig;en klicken</span>
<blockquote class="spoiler" id="spoiler-0716e642" style="display: none;">Es gibt ein Happy End.</blockquote>
<div class="body-attach"><div class="clear"></div></div>
</div>
</td>
</tr>
<tr>
<td>[spoiler=Autor]Spoiler Alarm[/spoiler]</td>
<td>
<div class="wall-item-container">
<strong class="spoiler">Autor hat geschrieben</strong><br>
<span id="spoiler-wrap-a893765a" class="spoiler-wrap fakelink" onclick="openClose('spoiler-a893765a');">Zum &ouml;ffnen/schlie&szlig;en klicken</span>
<blockquote class="spoiler" id="spoiler-a893765a" style="display: none;">Spoiler Alarm</blockquote>
<div class="body-attach"><div class="clear"></div></div>
</div>
</td>
</tr>
<tr>
<td>[hr] (horizontale Linie)</td>
<td><hr></td>
</tr>
</table>
### &Uuml;berschriften
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Ergebnis</th>
</tr>
<tr>
<td>[h1]Titel 1[/h1]</td>
<td><h1>Titel 1</h1></td>
</tr>
<tr>
<td>[h2]Titel 2[/h2]</td>
<td><h2>Titel 2</h2></td>
</tr>
<tr>
<td>[h3]Titel 3[/h3]</td>
<td><h3>Titel 3</h3></td>
</tr>
<tr>
<td>[h4]Titel 4[/h4]</td>
<td><h4>Titel 4</h4></td>
</tr>
<tr>
<td>[h5]Titel 5[/h5]</td>
<td><h5>Titel 5</h5></td>
</tr>
<tr>
<td>[h6]Titel 6[/h6]</td>
<td><h6>Titel 6</h6></td>
</tr>
</table>
### Tabellen
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Ergebnis</th>
</tr>
<tr>
<td>[table]<br>
&nbsp;&nbsp;[tr]<br>
&nbsp;&nbsp;&nbsp;&nbsp;[th]Kopfzeile 1[/th]<br>
&nbsp;&nbsp;&nbsp;&nbsp;[th]Kopfzeile 2[/th]<br>
&nbsp;&nbsp;&nbsp;&nbsp;[th]Kopfzeile 2[/th]<br>
&nbsp;&nbsp;[/tr]<br>
&nbsp;&nbsp;[tr]<br>
&nbsp;&nbsp;&nbsp;&nbsp;[td]Zelle 1[/td]<br>
&nbsp;&nbsp;&nbsp;&nbsp;[td]Zelle 2[/td]<br>
&nbsp;&nbsp;&nbsp;&nbsp;[td]Zelle 3[/td]<br>
&nbsp;&nbsp;[/tr]<br>
&nbsp;&nbsp;[tr]<br>
&nbsp;&nbsp;&nbsp;&nbsp;[td]Zelle 4[/td]<br>
&nbsp;&nbsp;&nbsp;&nbsp;[td]Zelle 5[/td]<br>
&nbsp;&nbsp;&nbsp;&nbsp;[td]Zelle 6[/td]<br>
&nbsp;&nbsp;[/tr]<br>
[/table]</td>
<td>
<table>
<tbody>
<tr>
<th>Kopfzeile 1</th>
<th>Kopfzeile 2</th>
<th>Kopfzeile 3</th>
</tr>
<tr>
<td>Zelle 1</td>
<td>Zelle 2</td>
<td>Zelle 3</td>
</tr>
<tr>
<td>Zelle 4</td>
<td>Zelle 5</td>
<td>Zelle 6</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td>[table border=0]</td>
<td>
<table border="0">
<tbody>
<tr>
<th>Kopfzeile 1</th>
<th>Kopfzeile 2</th>
<th>Kopfzeile 3</th>
</tr>
<tr>
<td>Zelle 1</td>
<td>Zelle 2</td>
<td>Zelle 3</td>
</tr>
<tr>
<td>Zelle 4</td>
<td>Zelle 5</td>
<td>Zelle 6</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td>[table border=1]</td>
<td>
<table border="1">
<tbody>
<tr>
<th>Kopfzeile 1</th>
<th>Kopfzeile 2</th>
<th>Kopfzeile 3</th>
</tr>
<tr>
<td>Zelle 1</td>
<td>Zelle 2</td>
<td>Zelle 3</td>
</tr>
<tr>
<td>Zelle 4</td>
<td>Zelle 5</td>
<td>Zelle 6</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
### Listen
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Ergebnis</th>
</tr>
<tr>
<td>[ul]<br>
&nbsp;&nbsp;[li] Erstes Listenelement<br>
&nbsp;&nbsp;[li] Zweites Listenelement<br>
[/ul]<br>
[list]<br>
&nbsp;&nbsp;[*] Erstes Listenelement<br>
&nbsp;&nbsp;[*] Zweites Listenelement<br>
[/list]</td>
<td>
<ul class="listbullet" style="list-style-type: circle;">
<li>Erstes Listenelement</li>
<li>Zweites Listenelement</li>
</ul>
</td>
</tr>
<tr>
<td>[ol]<br>
&nbsp;&nbsp;[*] Erstes Listenelement<br>
&nbsp;&nbsp;[*] Zweites Listenelement<br>
[/ol]<br>
[list=1]<br>
&nbsp;&nbsp;[*] Erstes Listenelement<br>
&nbsp;&nbsp;[*] Zweites Listenelement<br>
[/list]</td>
<td>
<ul class="listdecimal" style="list-style-type: decimal;">
<li> Erstes Listenelement</li>
<li> Zweites Listenelement</li>
</ul>
</td>
</tr>
<tr>
<td>[list=]<br>
&nbsp;&nbsp;[*] Erstes Listenelement<br>
&nbsp;&nbsp;[*] Zweites Listenelement<br>
[/list]</td>
<td>
<ul class="listnone" style="list-style-type: none;">
<li> Erstes Listenelement</li>
<li> Zweites Listenelement</li>
</ul>
</td>
</tr>
<tr>
<td>[list=i]<br>
&nbsp;&nbsp;[*] Erstes Listenelement<br>
&nbsp;&nbsp;[*] Zweites Listenelement<br>
[/list]</td>
<td>
<ul class="listlowerroman" style="list-style-type: lower-roman;">
<li> Erstes Listenelement</li>
<li> Zweites Listenelement</li>
</ul>
</td>
</tr>
<tr>
<td>[list=I]<br>
&nbsp;&nbsp;[*] Erstes Listenelement<br>
&nbsp;&nbsp;[*] Zweites Listenelement<br>
[/list]</td>
<td>
<ul class="listupperroman" style="list-style-type: upper-roman;">
<li> Erstes Listenelement</li>
<li> Zweites Listenelement</li>
</ul>
</td>
</tr>
<tr>
<td>[list=a]<br>
&nbsp;&nbsp;[*] Erstes Listenelement<br>
&nbsp;&nbsp;[*] Zweites Listenelement<br>
[/list]</td>
<td>
<ul class="listloweralpha" style="list-style-type: lower-alpha;">
<li> Erstes Listenelement</li>
<li> Zweites Listenelement</li>
</ul>
</td>
</tr>
<tr>
<td>[list=A]<br>
&nbsp;&nbsp;[*] Erstes Listenelement<br>
&nbsp;&nbsp;[*] Zweites Listenelement<br>
[/list]</td>
<td>
<ul class="listupperalpha" style="list-style-type: upper-alpha;">
<li> Erstes Listenelement</li>
<li> Zweites Listenelement</li>
</ul>
</td>
</tr>
</table>
## Einbetten
Du kannst Videos, Musikdateien und weitere Dinge in Beitr&auml;gen einbinden.
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Ergebnis</th>
</tr>
<tr>
<td>[video]url[/video]</td>
<td>Wobei die *url* eine URL von youtube, vimeo, soundcloud oder einer anderen Plattform sein kann, die die opengraph Spezifikationen unterst&uuml;tzt.</td>
</tr>
<tr>
<td>[video]URL der Videodatei[/video]
[audio]URL der Musikdatei[/audio]</td>
<td>Die komplette URL einer ogg/ogv/oga/ogm/webm/mp4/mp3 Datei angeben, diese wird dann mit einem HTML5-Player angezeigt.</td>
</tr>
<tr>
<td>[youtube]Youtube URL[/youtube]</td>
<td>Youtube Video mittels OEmbed anzeigen. Kann u.U, den Player nicht einbetten.</td>
</tr>
<tr>
<td>[youtube]Youtube video ID[/youtube]</td>
<td>Youtube-Player im iframe einbinden.</td>
</tr>
<tr>
<td>[vimeo]Vimeo URL[/vimeo]</td>
<td>Vimeo Video mittels OEmbed anzeigen. Kann u.U, den Player nicht einbetten.</td>
</tr>
<tr>
<td>[vimeo]Vimeo video ID[/vimeo]</td>
<td>Vimeo-Player im iframe einbinden.</td>
</tr>
<tr>
<td>[embed]URL[/embed]</td>
<td>OEmbed rich content einbetten.</td>
</tr>
<tr>
<td>[iframe]URL[/iframe]</td>
<td>General embed, iframe size is limited by the theme size for video players.</td>
</tr>
<tr>
<td>[url]*url*[/url]</td>
<td>Wenn *url* die OEmbed- oder Opengraph-Spezifikationen unterst&uuml;tzt, wird das Objekt eingebettet (z.B. Dokumente von scribd).
Ansonsten wird der Titel der Seite mit der URL verlinkt.</td>
</tr>
</table>
## Karten
Das Einbetten von Karten ben&ouml;tigt das "openstreetmap" oder das "Google Maps" Addon.
Wenn keines der Addons aktiv ist, werden stattde&szlig;en die Kordinaten angezeigt-
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Ergebnis</th>
</tr>
<tr>
<td>[map]Adresse[/map]</td>
<td>Bindet eine Karte ein, auf der die angegebene Adresse zentriert ist.</td>
</tr>
<tr>
<td>[map=lat,long]</td>
<td>Bindet eine Karte ein, die auf die angegebenen Koordinaten zentriert ist.</td>
</tr>
<tr>
<td>[map]</td>
<td>Bindet eine Karte ein, die auf die Position des Beitrags zentriert ist.</td>
</tr>
</table>
## Zusammenfassungen f&uuml;r lange Beitr&auml;ge
Wenn du deine Beitr&auml;ge auf anderen Netzwerken von Drittanbietern verbreiten m&ouml;chtest, z.B. Twitter, k&ouml;nntest du Probleme mit deren Zeichenbegrenzung haben.
Friendica verwendet einen semi-inelligenten Mechanismus um passende Zusammenfassungen zu erstellen.
Du kannst allerdings auch selbst die Zusammenfassungen erstellen, die auf den unterschiedlichen Netzwerken angezeigt werden.
Um dies zu tun, verwendest du den [abstract]-Tag.
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Ergebnis</th>
</tr>
<tr>
<td>[abstract]Unglaublich interessant! Muss man gesehen haben! Unbedingt dem Link folgen![/abstract]<br>
Ich m&ouml;chte euch eine unglaublich langweilige Geschichte erz&auml;hlen, die ihr sicherlich niemals h&ouml;ren wolltet.</td>
<td>Auf Twitter w&uuml;rde folgender Text verlffentlicht werden <blockquote>Unglaublich interessant! Muss man gesehen haben! Unbedingt dem Link folgen!</blockquote>
Wohingegen auf Friendica folgendes stehen w&uuml;rde <blockquote>Ich m&ouml;chte euch eine unglaublich langweilige Geschichte erz&auml;hlen, die ihr sicherlich niemals h&ouml;ren wolltet.</blockquote></td>
</tr>
</table>
Wenn du magst, kannst du auch unterschiedliche Zusammenfassungen f&uuml;r die unterschiedlichen Netzwerke verwenden.
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Ergebnis</th>
</tr>
<tr>
<td>
[abstract]Hey Leute, hier sind meines neuesten Bilder![/abstract]<br>
[abstract=twit]Hallo liebe Twitter Follower. Wollt ihr meine neuesten Bilder sehen?[/abstract]<br>
[abstract=apdn]Moin liebe Follower auf ADN. Ich habe einige neue Bilder gemacht, die ich euch gerne zeigen will.[/abstract]<br>
Heute war ich im Wald unterwegs und habe einige wirklich sch&ouml;ne Bilder gemacht...</td>
<td>F&uuml;r Twitter und App.net wird Friendica in diesem Fall die speziell definierten Zusammenfassungen Verwenden. F&uuml;r andere Netzwerke (wie z.B. bei der Verwendung des GNU Social Konnektors zum Ver&ouml;ffentlichen auf deinen GNU Social Account) w&uuml;rde die allgemeine Zusammenfassung verwenden.</td>
</tr>
</table>
Wenn du beispielsweise den "buffer"-Konnektor verwendest um Beitr&auml;ge nach Facebook und Google+ zu senden, dort aber nicht den gesamten Blogbeitrag posten willst sondern nur einen Anrei&szlig;er, kannst du dies mit dem [abstract]-Tag realisieren.
Bei Netzwerken wie Facebook oder Google+, die selbst kein Zeichenlimit haben wird das [abstract]-Element allerdings nicht grunds&auml;tzlich verwendet.
Daher m&uuml;ssen diese Netzwerke explizit genannt werden.
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Ergebnis</th>
</tr>
<tr>
<td>
[abstract]Dieser Tage hatte ich eine ungew&ouml;hnliche Begegnung...[/abstract]<br>
[abstract=goog]Hey liebe Google+ Follower. Habt ich schon meinen neuesten Blog-Beitrag gelesen?[/abstract]<br>
[abstract=face]Hallo liebe Facebook Freunde. Letztens ist mir etwas wirklich sch&ouml;nes pa&szlig;iert.[/abstract]<br>
Als ich die Bilder im Wald aufgenommen habe, hatte ich eine wirklich ungew&ouml;hnliche Begegnung...</td>
<td>Auf Google und Facebook w&uuml;rde nun die entsprechende Zusammenfassung verbreitet. F&uuml;r andere Netzwerke w&uuml;rde die allgemeine Zusammenfassung verwendet werden.<br>
<br>Auf Friendica wird weiterhin keine Zusammenfassung angezeigt.</td>
</tr>
</table>
F&uuml;r Verbindungen zu Netzwerken, zu denen Friendica den HTML Code postet, wie Tumblr, Wordpress oder Pump.io wird das [abstract] Element nicht verwendet.
Bei nativen Verbindungen; das hei&szlig;t zu z.B. Friendica, Hubzilla, Diaspora oder GNU Social Kontakten; wird der ungek&uuml;rzte Beitrag &uuml;bertragen.
Die Instanz des Kontakts k&uuml;mmert sich um die Darstellung.
## Special
<table class="bbcodes">
<tr>
<th>BBCode</th>
<th>Ergebnis</th>
</tr>
<tr>
<td>Wenn du verhindern m&ouml;chtest, da&szlig; der BBCode in einer Nachricht interpretiert wird, kannst du die [noparse], [nobb] oder [pre] Tag verwenden:<br>
<ul>
<li>[noparse][b]fett[/b][/noparse]</li>
<li>[nobb][b]fett[/b][/nobb]</li>
<li>[pre][b]fett[/b][/pre]</li>
</ul>
</td>
<td>[b]fett[/b]</td>
</tr>
<tr>
<td>[nosmile] kann verwendet werden um f&uuml;r einen Beitrag das umsetzen von Smilies zu verhindern.<br>
<br>
[nosmile] ;-) :-O
</td>
<td>;-) :-O</td>
</tr>
<tr>
<td>Benutzerdefinierte Inline-Styles<br>
<br>
[style=text-shadow: 0 0 4px #CC0000;]Du kannst alle CSS-Eigenschaften eines Blocks &auml;ndern-[/style]</td>
<td><span style="text-shadow: 0 0 4px #cc0000;;">Du kannst alle CSS-Eigenschaften eines Blocks &auml;ndern-</span></td>
</tr>
<tr>
<td>Benutzerdefinierte CSS Klassen<br>
<br>
[class=custom]Wenn die vergebene Klasse in den CSS Anweisungen existiert, wird sie angewandt.[/class]</td>
<td><pre>&lt;span class="custom"&gt;Wenn die<br>
vergebene Klasse in den CSS Anweisungen<br>
existiert,wird sie angewandt.&lt;/span&gt;</pre></td>
</tr>
</table>
<pre>[b]fett[/b]</pre> : <strong>fett</strong>
<pre>[i]kursiv[/i]</pre> : <em>kursiv</em>
<pre>[u]unterstrichen[/u]</pre> : <u>unterstrichen</u>
<pre>[s]durchgestrichen[/s]</pre> : <strike>durchgestrichen</strike>
<pre>[color=red]rot[/color]</pre> : <span style="color: red;">rot</span>
<pre>[url=http://www.friendica.com]Friendica[/url]</pre> : <a href="http://www.friendica.com" target="external-link">Friendica</a>
<pre>[img]http://friendica.com/sites/default/files/friendika-32.png[/img]</pre> : <img src="http://friendica.com/sites/default/files/friendika-32.png" alt="Immagine/foto">
<pre>[size=xx-small]kleiner Text[/size]</pre> : <span style="font-size: xx-small;">kleiner Text</span>
<pre>[size=xx-large]gro&szlig; Text[/size]</pre> : <span style="font-size: xx-large;">gro&szlig;er Text</span>
<pre>[size=20]exakte Textgr&ouml;&szlig;e[/size] (Textgr&ouml;&szlig;e kann jede Zahl sein, in Pixeln)</pre> : <span style="font-size: 20px;">exakte Gr&ouml;&szlig;e</span>
Block Tags
-----
<pre>[code]Code[/code]</pre>
<code>Code</code>
<p style="clear:both;">&nbsp;</p>
<pre>[code=php]function text_highlight($s,$lang)[/code]</pre>
<code><div class="hl-main"><ol class="hl-main"><li><span class="hl-code">&nbsp;</span><span class="hl-reserved">function</span><span class="hl-code"> </span><span class="hl-identifier">text_highlight</span><span class="hl-brackets">(</span><span class="hl-var">$s</span><span class="hl-code">,</span><span class="hl-var">$lang</span><span class="hl-brackets">)</span></li></ol></div></code>
<p style="clear:both;">&nbsp;</p>
<pre>[quote]Zitat[/quote]</pre>
<blockquote>Zitat</blockquote>
<p style="clear:both;">&nbsp;</p>
<pre>[quote=Autor]Der Autor? Ich? Nein, nein, nein...[/quote]</pre>
<strong class="author">Autor hat geschrieben:</strong><blockquote>Der Autor? Ich? Nein, nein, nein...</blockquote>
<p style="clear:both;">&nbsp;</p>
<pre>[center]zentrierter Text[/center]</pre>
<div style="text-align:center;">zentrierter Text</div>
<p style="clear:both;">&nbsp;</p>
<pre>Wer überrascht werden möchte sollte nicht weiter lesen.[spoiler]Es gibt ein Happy End.[/spoiler]</pre>
Wer überrascht werden möchte sollte nicht weiter lesen.<br />*klicken zum öffnen/schließen*
(Der Text zweischen dem öffnenden und dem schließenden Teil des spoiler Tags wird nicht angezeigt, bis der Link angeklickt wurde. In dem Fall wird *"Es gibt ein Happy End."* also erst angezeigt, wenn der Spoiler verraten wird.)
<p style="clear:both;">&nbsp;</p>
**Tabelle**
<pre>[table border=1]
[tr]
[th]Tabellenzeile[/th]
[/tr]
[tr]
[td]haben &Uuml;berschriften[/td]
[/tr]
[/table]</pre>
<table border="1"><tbody><tr><th>Tabellenzeile</th></tr><tr><td>haben &Uuml;berschriften</td></tr></tbody></table>
<p style="clear:both;">&nbsp;</p>
**Listen**
<pre>[list]
[*] Erstes Listenelement
[*] Zweites Listenelement
[/list]</pre>
<ul class="listbullet" style="list-style-type: circle;">
<li> Erstes Listenelement<br>
</li>
<li> Zweites Listenelement</li>
</ul>
[list] ist Equivalent zu [ul] (unsortierte Liste).
[ol] kann anstelle von [list] verwendet werden um eine sortierte Liste zu erzeugen:
<pre>[ol]
[*] Erstes Listenelement
[*] Zweites Listenelement
[/ol]</pre>
<ul class="listdecimal" style="list-style-type: decimal;"><li>Erstes Listenelement<br></li><li> Zweites Listenelement</li></ul>
F&uuml;r weitere Optionen von sortierten Listen kann man den Stil der Numerierung der Liste definieren:
<pre>[list=1]</pre> : dezimal
<pre>[list=i]</pre> : r&ouml;misch, Kleinbuchstaben
<pre>[list=I]</pre> : r&ouml;misch, Gro&szlig;buchstaben
<pre>[list=a]</pre> : alphabetisch, Kleinbuchstaben
<pre>[list=A] </pre> : alphabethisch, Gro&szlig;buchstaben
Einbettung von Inhalten
------
Man kann viele Dinge, z.B. Video und Audio Dateine, in Nachrichten einbetten.
<pre>[video]url[/video]</pre>
<pre>[audio]url[/audio]</pre>
Wobei die *url* von youtube, vimeo, soundcloud oder einer anderen Seite stammen kann die die oembed oder opengraph Spezifikationen unterst&uuml;tzt.
Au&szlig;erdem kann *url* die genaue url zu einer ogg Datei sein, die dann per HTML5 eingebunden wird.
<pre>[url]*url*[/url]</pre>
Wenn *url* entweder oembed oder opengraph unterstützt wird das eingebettete Objekt (z.B. ein Dokument von scribd) eingebunden.
Der Titel der Seite mit einem Link zur *url* wird ebenfalls angezeigt.
Um eine Karte in einen Beitrag einzubinden, muss das *openstreetmap* Addon aktiviert werden. Ist dies der Fall, kann mit
<pre>[map]Broadway 26, New York[/map]</pre>
eine Karte von [OpenStreetmap](http://openstreetmap.org) eingebettet werden. Zur Identifikation des Ortes können entweder seine Koordinaten in der Form
<pre>[map=lat,long]</pre>
oder eine Adresse in obiger Form verwendet werden.
Zusammenfassung für längere Beiträge
------------------------------------
Wenn man seine Beiträge über mehrere Netzwerke verbreiten möchte, hat man häufig das Problem, dass diese Netzwerke z.B. eine Längenbeschränkung haben.
(Z.B. Twitter).
Friendica benutzt zum Erzeugen eines Anreißtextes eine halbwegs intelligente Logik.
Es kann aber dennoch von Interesse sein, eine eigene Zusammenfassung zu erstellen, die nur auf dem Fremdnetzwerk dargestellt wird.
Dies geschieht mit dem [abstract]-Element.
Beispiel:
<pre>[abstract]Total spannend! Unbedingt diesen Link anklicken![/abstract]
Hier erzähle ich euch eine total langweilige Geschichte, die ihr noch
nie hören wolltet.</pre>
Auf Twitter würde das "Total spannend! Unbedingt diesen Link anklicken!" stehen, auf Friendica würde nur der Text nach "Hier erzähle ..." erscheinen.
Es ist sogar möglich, für einzelne Netzwerke eigene Zusammenfassungen zu erstellen:
<pre>
[abstract]Hallo Leute, hier meine neuesten Bilder![abstract]
[abstract=twit]Hallo Twitter-User, hier meine neuesten Bilder![abstract]
[abstract=apdn]Hallo App.net-User, hier meine neuesten Bilder![abstract]
Ich war heute wieder im Wald unterwegs und habe tolle Bilder geschossen ...
</pre>
Für Twitter und App.net nimmt das System die entsprechenden Texte.
Bei anderen Netzwerken, bei denen der Inhalt gekürzt wird (z.B. beim "statusnet"-Connector, der für das Posten nach GNU Social verwendet wird) wird dann die Zusammenfassung unter [abstract] verwendet.
Wenn man z.B. den "buffer"-Connector verwendet, um nach Facebook oder Google+ zu posten, kann man dieses Element ebenfalls verwenden, wenn man z.B. einen längeren Blogbeitrag erstellt hat, aber ihn nicht komplett in diese Netzwerke posten möchte.
Netzwerke wie Facebook oder Google+ sind nicht in der Postinglänge beschränkt.
Aus diesem Grund greift nicht die [abstract]-Zusammenfassung. Stattdessen muss man das Netzwerk explizit angeben:
<pre>
[abstract]Ich habe neulich wieder etwas erlebt, was ich euch mitteilen möchte.[abstract]
[abstract=goog]Hallo meine Google+-Kreislinge. Ich habe neulich wieder
etwas erlebt, was ich euch mitteilen möchte.[abstract]
[abstract=face]Hallo Facebook-Freunde! Ich habe neulich wieder etwas
erlebt, was ich euch mitteilen möchte.[abstract]
Beim Bildermachen im Wald habe ich neulich eine interessante Person
getroffen ... </pre>
Das [abstract]-Element greift nicht bei der nativen OStatus-Verbindung oder bei Connectoren, die den HTML-Text posten wie z.B. die Connectoren zu Tumblr, Wordpress oder Pump.io.
Spezielle Tags
-------
Wenn Du &uuml;ber BBCode Tags in einer Nachricht schreiben m&ouml;chtest, kannst Du [noparse], [nobb] oder [pre] verwenden um den BBCode Tags vor der Evaluierung zu sch&uuml;tzen:
<pre>[noparse][b]fett[/b][/noparse]</pre> : [b]fett[/b]

View file

@ -6,7 +6,7 @@ Bugs und Probleme
Du solltest jeden Bug und jedes Problem, den/das Du findest, zunächst dem Administrator (oder gegebenenfalls der Support-Seite) Deines Servers melden, statt auf der allgemeinen Bug-Seite.
Das erleichtert den Entwicklern ihre Arbeit (z. B. neue Features zu entwickeln), da sie sich nicht mit Fehlern beschäftigen müssen, mit denen sie nichts zu tun haben.
Wenn Du technisch versiert bist oder Dein Knoten keine Support-Seite hat, dann kannst Du den <a href="http://bugs.friendica.com/">Bug Tracker</a> nutzen.
Wenn Du technisch versiert bist oder Dein Knoten keine Support-Seite hat, dann kannst Du den <a href="https://github.com/friendica/friendica/issues">Bug Tracker</a> nutzen.
Bitte durchsuche zunächst die Seite, ob es bereits einen offenen Bug gibt, der Deiner Anfrage entspricht.
Liefere so viele Informationen wie möglich zu dem Bug.

View file

@ -20,38 +20,37 @@ Friendica - Dokumentation und Ressourcen
* [Community-Foren](help/Forums)
* [Chats](help/Chats)
* Weiterführende Informationen
* [Performance verbessern](help/Improve-Performance)
* [Account umziehen](help/Move-Account)
* [Account löschen](help/Remove-Account)
* [Bugs und Probleme](help/Bugs-and-Issues)
* [Häufig gestellte Fragen (FAQ)](help/FAQ)
**Technische Dokumentation**
**Dokumentation für Administratoren**
* [Installation](help/Install)
* [Konfigurationen & Admin-Panel](help/Settings)
* [Plugins](help/Plugins)
* [Konnektoren (Connectors) installieren (Twitter/GNU Social)](help/Installing-Connectors)
* [Installation eines ejabberd Servers (XMPP-Chat) mit synchronisierten Anmeldedaten](help/install-ejabberd) (EN)
* [Nachrichtenfluss](help/Message-Flow)
* [Betreibe deine Seite mit einem SSL-Zertifikat](help/SSL)
* [Entwickler](help/Developers)
* [Twitter/GNU Social API Functions](help/api) (EN)
* [Translation of Friendica](help/translations) (EN)
* [Konfigurationswerte, die nur in der .htconfig.php gesetzt werden können](help/htconfig) (EN)
* [Performance verbessern](help/Improve-Performance)
**Entwickler Dokumentation**
**Dokumentation für Entwickler**
* [Where to get started?](help/Developers-Intro)
* [Entwickler](help/Developers)
* [Where to get started?](help/Developers-Intro) (EN)
* [Help on Github](help/Github)
* [Help on Vagrant](help/Vagrant)
* [How to translate Friendica](help/translations)
* [How to translate Friendica](help/translations) (EN)
* [Bugs and Issues](help/Bugs-and-Issues)
* [Plugin Development](help/Plugins)
* [Theme Development](help/themes)
* [Smarty 3 Templates](help/smarty3-templates)
* [Protokoll Dokumentation](help/Protocol) (EN)
* [Datenbank-Schema](help/database)
* [Code-Referenz (mit doxygen generiert - setzt Cookies)](doc/html/)
* [Twitter/GNU Social API Functions](help/api) (EN)
**Externe Ressourcen**

View file

@ -6,7 +6,7 @@ Friendica Nachrichtenfluss
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).
Diejenigen, die den Nachrichtenfluss genauer verstehen wollen, sollten sich mindestens mit dem DFRN-Protokoll ([Dokument mit den DFRN Spezifikationen](https://github.com/friendica/friendica/blob/master/spec/dfrn2.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.
@ -24,7 +24,7 @@ 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
### 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).
@ -33,13 +33,13 @@ Sie fallen zurück auf eine tägliche Abfrage, wenn der Hub Übertragungsschwier
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.
### 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.
### 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).
@ -47,14 +47,14 @@ 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.
### 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.
### 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.

View file

@ -5,7 +5,7 @@ Friendica mit SSL nutzen
Disclaimer
---
**Dieses Dokument wurde im November 2015 aktualisiert.
**Dieses Dokument wurde im November 2016 aktualisiert.
SSL-Verschlüsselung ist sicherheitskritisch.
Das bedeutet, dass sich die empfohlenen Einstellungen schnell verändern.
Halte deine Installation auf dem aktuellen Stand und verlasse dich nicht darauf, dass dieses Dokument genau so schnell aktualisiert wird, wie sich Technologien verändern!**
@ -45,55 +45,15 @@ Sie installieren es für dich oder haben in der Weboberfläche eine einfache Upl
Um Geld zu sparen, kann es sich lohnen, dort auch nachzufragen, ob sie ein anderes Zertifikat, das du selbst beschaffst, für dich installieren würden.
Wenn ja, dann lies weiter.
Ein kostenloses StartSSL-Zertifikat besorgen
---
StartSSL ist eine Zertifizierungsstelle, die kostenlose Zertifikate ausstellt.
Sie sind für ein Jahr gültig und genügen für unsere Zwecke.
### Schritt 1: Client-Zertifikat erstellen
Wenn du dich erstmalig bei StartSSL anmeldest, erhältst du ein Zertifikat, das in deinem Browser installiert wird.
Du brauchst es, um dich bei StartSSL einzuloggen, auch wenn du später wiederkommst.
Dieses Client-Zertifikat hat nichts mit dem SSL-Zertifikat für deinen Server zu tun.
### Schritt 2: Email-Adresse und Domain validieren
Um fortzufahren musst du beweisen, dass du die Email-Adresse, die du angegeben hast, und die Domain, für die du das Zertifikat möchtest, besitzt.
Gehe in den "Validation wizard" und fordere einen Bestätigungslink per Mail an.
Dasselbe machst du auch für die Validierung der Domain.
### Schritt 3: Das Zertifikat bestellen
Gehe in den "Certificate wizard".
Wähle das Target Webserver.
Bei der ersten Abfrage der Domain gibst du deine Hauptdomain an.
Im nächsten Schritt kannst du eine Subdomain hinzufügen.
Ein Beispiel: Wenn die Adresse der Friendica-Instanz friendica.beispiel.net lautet, gibst du zuerst beispiel.net an und danach friendica.
Wenn du weißt, wie man einen openssl-Schlüssel und einen Certificate Signing Request (CSR) erstellt, tu das.
Kopiere den CSR in den Browser, um ihn von StartSSL signiert zu bekommen.
Wenn du nicht weißt, wie man Schlüssel und CSR erzeugt, nimm das Angebot von StartSSL an, beides für dich zu generieren.
Das bedeutet: StartSSL hat den Schlüssel zu deiner SSL-Verschlüsselung, aber das ist immer noch besser als gar kein Zertifikat.
Lade dein Zertifikat von der Website herunter.
(Oder im zweiten Fall: Lade Zertifikat und Schlüssel herunter.)
Um dein Zertifikat auf einem Webserver zu installieren, brauchst du noch ein oder zwei andere Dateien: sub.class1.server.ca.pem und ca.pem, auch von StartSSL.
Gehe in die Rubrik "Tool box" und lade "Class 1 Intermediate Server CA" und "StartCom Root CA (PEM encoded)" herunter.
Wenn du dein Zertifikat zu deinem Hosting-Provider schicken möchtest, brauchen Sie mindestens Zertifikat und Schlüssel.
Schick zur Sicherheit alle vier Dateien hin.
**Du solltest sie auf einem verschlüsselten Weg hinschicken!**
Wenn du deinen eigenen Server betreibst, lade die Dateien hoch und besuche das Mozilla-Wiki (Link unten).
Let's encrypt
---
Wenn du einen eigenen Server betreibst und den Nameserver kontrollierst, könnte auch die Initiative "Let's encrypt" interessant für dich werden.
Momentan ist deren Angebot noch nicht fertig.
Auf der [Website](https://letsencrypt.org/) kannst du dich über den Stand informieren.
Sie bietet nicht nur freie SSL Zertifikate sondern auch einen automatisierten Prozess zum Erneuern der Zertifikate.
Um letsencrypt Zertifikate verwenden zu können, musst du dir einen Client auf deinem Server installieren.
Eine Anleitung zum offiziellen Client findet du [hier](https://certbot.eff.org/).
Falls du dir andere Clients anschauen willst, kannst du einen Blick in diese [Liste von alternativen letsencrypt Clients](https://letsencrypt.org/docs/client-options/).
Webserver-Einstellungen
---

View file

@ -1,5 +1,7 @@
# Settings
* [Zur Startseite der Hilfe](help)
Wenn du der Administrator einer Friendica Instanz bist, hast du Zugriff auf das so genannte **Admin Panel** in dem du die Friendica Instanz konfigurieren kannst,
Auf der Startseite des Admin Panels werden die Informationen zu der Instanz zusammengefasst.
@ -9,8 +11,9 @@ Diese Zahl sollte sich relativ schnell sinken.
Die zweite Zahl gibt die Anzahl von Nachrichten an, die nicht zugestellt werden konnten.
Die Zustellung wird zu einem späteren Zeitpunkt noch einmal versucht.
Unter dem Punkt "Warteschlange Inspizieren" kannst du einen schnellen Blick auf die zweite Warteschlange werfen.
Solltest du für die Hintergrundprozesse die Worker aktiviert haben, könntest du eine dritte Zahl angezeigt bekommen.
Solltest du für die Hintergrundprozesse die Worker aktiviert haben, wird eine dritte Zahl angezeigt.
Diese repräsentiert die Anzahl der Aufgaben, die die Worker noch vor sich haben.
Die Aufgaben der Worker sind priorisiert und werden anhand dieser Prioritäten abgearbeitet.
Des weiteren findest du eine Übersicht über die Accounts auf dem Friendica Knoten, die unter dem Punkt "Nutzer" moderiert werden können.
Sowie eine Liste der derzeit aktivierten Addons.

View file

@ -52,6 +52,8 @@ Cleanzero <img src="doc/img/editor_zero.png" alt="cleanzero.png" style="padding
Darkbubble <img src="doc/img/editor_darkbubble.png" alt="darkbubble.png" style="padding-left: 14px; vertical-align:middle;"> <i>(inkl. smoothly, testbubble)</i>
Frio <img src="doc/img/editor_frio.png" alt="frio.png" style="padding-left: 44px; vertical-align:middle;">
Frost <img src="doc/img/editor_frost.png" alt="frost.png" style="padding-left: 42px; vertical-align:middle;">
Vier <img src="doc/img/editor_vier.png" alt="vier.png" style="padding-left: 44px; vertical-align:middle;"> <i>(inkl. dispy)</i>

View file

@ -1,102 +1,105 @@
Config values that can only be set in .htconfig.php
===================================================
There are some config values that haven't found their way into the administration page. This has several reasons. Maybe they are part of a
current development that isn't considered stable and will be added later in the administration page when it is considered safe. Or it triggers
something that isn't expected to be of public interest. Or it is for testing purposes only.
* [Home](help)
**Attention:** Please be warned that you shouldn't use one of these values without the knowledge what it could trigger. Especially don't do that with
undocumented values.
There are some config values that haven't found their way into the administration page.
This has several reasons.
Maybe they are part of a current development that isn't considered stable and will be added later in the administration page when it is considered safe.
Or it triggers something that isn't expected to be of public interest. Or it is for testing purposes only.
The header of the section describes the category, the value is the parameter. Example: To set the directory value please add this
line to your .htconfig.php:
**Attention:** Please be warned that you shouldn't use one of these values without the knowledge what it could trigger.
Especially don't do that with undocumented values.
The header of the section describes the category, the value is the parameter.
Example: To set the directory value please add this line to your .htconfig.php:
$a->config['system']['directory'] = 'http://dir.friendi.ca';
## jabber ##
* **debug** (Boolean) - Enable debug level for the jabber account synchronisation.
* **logfile** - Logfile for the jabber account synchronisation.
## system ##
## Jabber ##
* debug (Boolean) - Enable debug level for the jabber account synchronisation.
* logfile - Logfile for the jabber account synchronisation.
## System ##
* birthday_input_format - Default value is "ymd".
* block_local_dir (Boolean) - Blocks the access to the directory of the local users.
* default_service_class -
* delivery_batch_count - Number of deliveries per process. Default value is 1. (Disabled when using the worker)
* diaspora_test (Boolean) - For development only. Disables the message transfer.
* directory - The path to global directory. If not set then "http://dir.friendi.ca" is used.
* disable_email_validation (Boolean) - Disables the check if a mail address is in a valid format and can be resolved via DNS.
* disable_url_validation (Boolean) - Disables the DNS lookup of an URL.
* event_input_format - Default value is "ymd".
* ignore_cache (Boolean) - For development only. Disables the item cache.
* like_no_comment (Boolean) - Don't update the "commented" value of an item when it is liked.
* local_block (Boolean) - Used in conjunction with "block_public".
* local_search (Boolean) - Blocks the search for not logged in users to prevent crawlers from blocking your system.
* max_connections - The poller process isn't started when the maximum level of the possible database connections are used. When the system can't detect the maximum numbers of connection then this value can be used.
* max_connections_level - The maximum level of connections that are allowed to let the poller start. It is a percentage value. Default value is 75.
* max_contact_queue - Default value is 500.
* max_batch_queue - Default value is 1000.
* max_processes_backend - Maximum number of concurrent database processes for background tasks. Default value is 5.
* max_processes_frontend - Maximum number of concurrent database processes for foreground tasks. Default value is 20.
* no_oembed (Boolean) - Don't use OEmbed to fetch more information about a link.
* no_oembed_rich_content (Boolean) - Don't show the rich content (e.g. embedded PDF).
* no_smilies (Boolean) - Don't show smilies.
* no_view_full_size (Boolean) - Don't add the link "View full size" under a resized image.
* optimize_items (Boolean) - Triggers an SQL command to optimize the item table before expiring items.
* ostatus_poll_timeframe - Defines how old an item can be to try to complete the conversation with it.
* paranoia (Boolean) - Log out users if their IP address changed.
* permit_crawling (Boolean) - Restricts the search for not logged in users to one search per minute.
* free_crawls - Number of "free" searches when "permit_crawling" is activated (Default value is 10)
* crawl_permit_period - Period in seconds between allowed searches when the number of free searches is reached and "permit_crawling" is activated (Default value is 60)
* png_quality - Default value is 8.
* proc_windows (Boolean) - Should be enabled if Friendica is running under Windows.
* proxy_cache_time - Time after which the cache is cleared. Default value is one day.
* pushpoll_frequency -
* qsearch_limit - Default value is 100.
* relay_server - Experimental Diaspora feature. Address of the relay server where public posts should be send to. For example https://podrelay.net
* relay_subscribe (Boolean) - Enables the receiving of public posts from the relay. They will be included in the search and on the community page when it is set up to show all public items.
* relay_scope - Can be "all" or "tags". "all" means that every public post should be received. "tags" means that only posts with selected tags should be received.
* relay_server_tags - Comma separated list of tags for the "tags" subscription (see "relay_scrope")
* relay_user_tags (Boolean) - If enabled, the tags from the saved searches will used for the "tags" subscription in addition to the "relay_server_tags".
* remove_multiplicated_lines (Boolean) - If enabled, multiple linefeeds in items are stripped to a single one.
* show_unsupported_addons (Boolean) - Show all addons including the unsupported ones.
* show_unsupported_themes (Boolean) - Show all themes including the unsupported ones.
* throttle_limit_day - Maximum number of posts that a user can send per day with the API.
* throttle_limit_week - Maximum number of posts that a user can send per week with the API.
* throttle_limit_month - Maximum number of posts that a user can send per month with the API.
* wall-to-wall_share (Boolean) - Displays forwarded posts like "wall-to-wall" posts.
* worker_cooldown - Cooldown time after each worker function call. Default value is 0 seconds.
* xrd_timeout - Timeout for fetching the XRD links. Default value is 20 seconds.
* **allowed_link_protocols** (Array) - Allowed protocols in links URLs, add at your own risk. http is always allowed.
* **birthday_input_format** - Default value is "ymd".
* **block_local_dir** (Boolean) - Blocks the access to the directory of the local users.
* **default_service_class** -
* **delivery_batch_count** - Number of deliveries per process. Default value is 1. (Disabled when using the worker)
* **diaspora_test** (Boolean) - For development only. Disables the message transfer.
* **directory** - The path to global directory. If not set then "http://dir.friendi.ca" is used.
* **disable_email_validation** (Boolean) - Disables the check if a mail address is in a valid format and can be resolved via DNS.
* **disable_url_validation** (Boolean) - Disables the DNS lookup of an URL.
* **event_input_format** - Default value is "ymd".
* **frontend_worker_timeout** - Value in minutes after we think that a frontend task was killed by the webserver. Default value is 10.
* **ignore_cache** (Boolean) - For development only. Disables the item cache.
* **like_no_comment** (Boolean) - Don't update the "commented" value of an item when it is liked.
* **local_block** (Boolean) - Used in conjunction with "block_public".
* **local_search** (Boolean) - Blocks the search for not logged in users to prevent crawlers from blocking your system.
* **max_connections** - The poller process isn't started when the maximum level of the possible database connections are used. When the system can't detect the maximum numbers of connection then this value can be used.
* **max_connections_level** - The maximum level of connections that are allowed to let the poller start. It is a percentage value. Default value is 75.
* **max_contact_queue** - Default value is 500.
* **max_batch_queue** - Default value is 1000.
* **max_processes_backend** - Maximum number of concurrent database processes for background tasks. Default value is 5.
* **max_processes_frontend** - Maximum number of concurrent database processes for foreground tasks. Default value is 20.
* **memcache** (Boolean) - Use memcache. To use memcache the PECL extension "memcache" has to be installed and activated.
* **memcache_host** - Hostname of the memcache daemon. Default is '127.0.0.1'.
* **memcache_port** - Portnumberof the memcache daemon. Default is 11211.
* **no_oembed** (Boolean) - Don't use OEmbed to fetch more information about a link.
* **no_oembed_rich_content** (Boolean) - Don't show the rich content (e.g. embedded PDF).
* **no_smilies** (Boolean) - Don't show smilies.
* **no_view_full_size** (Boolean) - Don't add the link "View full size" under a resized image.
* **optimize_items** (Boolean) - Triggers an SQL command to optimize the item table before expiring items.
* **ostatus_poll_timeframe** - Defines how old an item can be to try to complete the conversation with it.
* **paranoia** (Boolean) - Log out users if their IP address changed.
* **permit_crawling** (Boolean) - Restricts the search for not logged in users to one search per minute.
* **profiler** (Boolean) - Enable internal timings to help optimize code. Needed for "rendertime" addon. Default is false.
* **free_crawls** - Number of "free" searches when "permit_crawling" is activated (Default value is 10)
* **crawl_permit_period** - Period in seconds between allowed searches when the number of free searches is reached and "permit_crawling" is activated (Default value is 60)
* **png_quality** - Default value is 8.
* **proc_windows** (Boolean) - Should be enabled if Friendica is running under Windows.
* **proxy_cache_time** - Time after which the cache is cleared. Default value is one day.
* **pushpoll_frequency** -
* **qsearch_limit** - Default value is 100.
* **relay_server** - Experimental Diaspora feature. Address of the relay server where public posts should be send to. For example https://podrelay.net
* **relay_subscribe** (Boolean) - Enables the receiving of public posts from the relay. They will be included in the search and on the community page when it is set up to show all public items.
* **relay_scope** - Can be "all" or "tags". "all" means that every public post should be received. "tags" means that only posts with selected tags should be received.
* **relay_server_tags** - Comma separated list of tags for the "tags" subscription (see "relay_scrope")
* **relay_user_tags** (Boolean) - If enabled, the tags from the saved searches will used for the "tags" subscription in addition to the "relay_server_tags".
* **remove_multiplicated_lines** (Boolean) - If enabled, multiple linefeeds in items are stripped to a single one.
* **show_unsupported_addons** (Boolean) - Show all addons including the unsupported ones.
* **show_unsupported_themes** (Boolean) - Show all themes including the unsupported ones.
* **throttle_limit_day** - Maximum number of posts that a user can send per day with the API.
* **throttle_limit_week** - Maximum number of posts that a user can send per week with the API.
* **throttle_limit_month** - Maximum number of posts that a user can send per month with the API.
* **wall-to-wall_share** (Boolean) - Displays forwarded posts like "wall-to-wall" posts.
* **worker_cooldown** - Cooldown time after each worker function call. Default value is 0 seconds.
* **xrd_timeout** - Timeout for fetching the XRD links. Default value is 20 seconds.
## service_class ##
* upgrade_link -
* **upgrade_link** -
## experimentals ##
* exp_themes (Boolean) - Show experimental themes as well.
* **exp_themes** (Boolean) - Show experimental themes as well.
## theme ##
* hide_eventlist (Boolean) - Don't show the birthdays and events on the profile and network page
* **hide_eventlist** (Boolean) - Don't show the birthdays and events on the profile and network page
# Administrator Options #
Enabling the admin panel for an account, and thus making the account holder
admin of the node, is done by setting the variable
Enabling the admin panel for an account, and thus making the account holder admin of the node, is done by setting the variable
$a->config['admin_email'] = "someone@example.com";
where you have to match the email address used for the account with the one you
enter to the .htconfig file. If more then one account should be able to access
the admin panel, seperate the email addresses with a comma.
Where you have to match the email address used for the account with the one you enter to the .htconfig file.
If more then one account should be able to access the admin panel, seperate the email addresses with a comma.
$a->config['admin_email'] = "someone@example.com,someonelese@example.com";
If you want to have a more personalized closing line for the notification
emails you can set a variable for the admin_name.
If you want to have a more personalized closing line for the notification emails you can set a variable for the admin_name.
$a->config['admin_name'] = "Marvin";

BIN
doc/img/editor_frio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,6 +1,8 @@
Friendica translations
======================
* [Home](help)
Translation Process
-------------------
@ -24,12 +26,12 @@ If you want to get your work into the source tree yourself, feel free to do so a
The process is simple and friendica ships with all the tools necessary.
The location of the translated files in the source tree is
/view/LNG-CODE/
/view/lang/LNG-CODE/
where LNG-CODE is the language code used, e.g. de for German or fr for French.
The translated strings come as a "message.po" file from transifex which needs to be translated into the PHP file friendica uses.
To do so, place the file in the directory mentioned above and use the "po2php" utility from the util directory of your friendica installation.
Assuming you want to convert the German localization which is placed in view/de/message.po you would do the following.
Assuming you want to convert the German localization which is placed in view/lang/de/message.po you would do the following.
1. Navigate at the command prompt to the base directory of your
friendica installation
@ -37,9 +39,9 @@ Assuming you want to convert the German localization which is placed in view/de/
2. Execute the po2php script, which will place the translation
in the strings.php file that is used by friendica.
$> php util/po2php.php view/de/messages.po
$> php util/po2php.php view/lang/de/messages.po
The output of the script will be placed at view/de/strings.php where
The output of the script will be placed at view/lang/de/strings.php where
friendica is expecting it, so you can test your translation immediately.
3. Visit your friendica page to check if it still works in the language you
@ -50,7 +52,7 @@ Assuming you want to convert the German localization which is placed in view/de/
not give any output if the file is ok but might give a hint for
searching the bug in the file.
$> php view/de/strings.php
$> php view/lang/de/strings.php
4. commit the two files with a meaningful commit message to your git
repository, push it to your fork of the friendica repository at github and

34
doc/upgrade.md Normal file
View file

@ -0,0 +1,34 @@
# Considerations before upgrading Friendica
* [Home](help)
## MySQL >= 5.7.4
Starting from MySQL version 5.7.4, the IGNORE keyword in ALTER TABLE statements is ignored.
This prevents automatic table deduplication if a UNIQUE index is added to a Friendica table's structure.
If a DB update fails for you while creating a UNIQUE index, make sure to manually deduplicate the table before trying the update again.
### Manual deduplication
There are two main ways of doing it, either by manually removing the duplicates or by recreating the table.
Manually removing the duplicates is usually faster if they're not too numerous.
To manually remove the duplicates, you need to know the UNIQUE index columns available in `database.sql`.
```SQL
SELECT GROUP_CONCAT(id), <index columns>, count(*) as count FROM users
GROUP BY <index columns> HAVING count >= 2;
/* delete or merge duplicate from above query */;
```
If there are too many rows to handle manually, you can create a new table with the same structure as the table with duplicates and insert the existing content with INSERT IGNORE.
To recreate the table you need to know the table structure available in `database.sql`.
```SQL
CREATE TABLE <table_name>_new <rest of the CREATE TABLE>;
INSERT IGNORE INTO <table_name>_new SELECT * FROM <table_name>;
DROP TABLE <table_name>;
RENAME TABLE <table_name>_new TO <table_name>;
```
This method is slower overall, but it is better suited for large numbers of duplicates.

View file

@ -66,7 +66,7 @@ $a->config['system']['allowed_themes'] = 'quattro,vier,duepuntozero';
// default system theme
$a->config['system']['theme'] = 'duepuntozero';
$a->config['system']['theme'] = 'vier';
// By default allow pseudonyms
@ -78,3 +78,6 @@ $a->config['system']['no_regfullname'] = true;
// Location of the global directory
$a->config['system']['directory'] = 'http://dir.friendi.ca';
// Allowed protocols in link URLs; HTTP protocols always are accepted
$a->config['system']['allowed_link_protocols'] = array('ftp', 'ftps', 'mailto', 'cid', 'gopher');

View file

@ -22,6 +22,7 @@ function user_remove($uid) {
$r[0]['nickname']
);
/// @todo Should be done in a background job since this likely will run into a time out
// don't delete yet, will be done later when contacts have deleted my stuff
// q("DELETE FROM `contact` WHERE `uid` = %d", intval($uid));
q("DELETE FROM `gcign` WHERE `uid` = %d", intval($uid));
@ -74,25 +75,10 @@ function contact_remove($id) {
return;
}
q("DELETE FROM `contact` WHERE `id` = %d",
intval($id)
);
q("DELETE FROM `item` WHERE `contact-id` = %d ",
intval($id)
);
q("DELETE FROM `photo` WHERE `contact-id` = %d ",
intval($id)
);
q("DELETE FROM `mail` WHERE `contact-id` = %d ",
intval($id)
);
q("DELETE FROM `event` WHERE `cid` = %d ",
intval($id)
);
q("DELETE FROM `queue` WHERE `cid` = %d ",
intval($id)
);
q("DELETE FROM `contact` WHERE `id` = %d", intval($id));
// Delete the rest in the background
proc_run(PRIORITY_LOW, 'include/remove_contact.php', $id);
}
@ -145,7 +131,6 @@ function terminate_friendship($user,$self,$contact) {
// This provides for the possibility that their database is temporarily messed
// up or some other transient event and that there's a possibility we could recover from it.
if(! function_exists('mark_for_death')) {
function mark_for_death($contact) {
if($contact['archive'])
@ -156,14 +141,24 @@ function mark_for_death($contact) {
dbesc(datetime_convert()),
intval($contact['id'])
);
}
else {
if ($contact['url'] != '') {
q("UPDATE `contact` SET `term-date` = '%s'
WHERE `nurl` = '%s' AND `term-date` <= '1000-00-00'",
dbesc(datetime_convert()),
dbesc(normalise_link($contact['url']))
);
}
} else {
/// @todo
/// We really should send a notification to the owner after 2-3 weeks
/// so they won't be surprised when the contact vanishes and can take
/// remedial action if this was a serious mistake or glitch
/// @todo
/// Check for contact vitality via probing
$expiry = $contact['term-date'] . ' + 32 days ';
if(datetime_convert() > datetime_convert('UTC','UTC',$expiry)) {
@ -171,31 +166,51 @@ function mark_for_death($contact) {
// archive them rather than delete
// though if the owner tries to unarchive them we'll start the whole process over again
q("update contact set `archive` = 1 where id = %d",
q("UPDATE `contact` SET `archive` = 1 WHERE `id` = %d",
intval($contact['id'])
);
q("UPDATE `item` SET `private` = 2 WHERE `contact-id` = %d AND `uid` = %d", intval($contact['id']), intval($contact['uid']));
//contact_remove($contact['id']);
if ($contact['url'] != '') {
q("UPDATE `contact` SET `archive` = 1 WHERE `nurl` = '%s'",
dbesc(normalise_link($contact['url']))
);
}
}
}
}}
}
if(! function_exists('unmark_for_death')) {
function unmark_for_death($contact) {
$r = q("SELECT `term-date` FROM `contact` WHERE `id` = %d AND `term-date` > '%s'",
intval($contact['id']),
dbesc('1000-00-00 00:00:00')
);
// We don't need to update, we never marked this contact as dead
if (!dbm::is_result($r)) {
return;
}
// It's a miracle. Our dead contact has inexplicably come back to life.
q("UPDATE `contact` SET `term-date` = '%s' WHERE `id` = %d",
dbesc('0000-00-00 00:00:00'),
intval($contact['id'])
);
}}
if ($contact['url'] != '') {
q("UPDATE `contact` SET `term-date` = '%s' WHERE `nurl` = '%s'",
dbesc('0000-00-00 00:00:00'),
dbesc(normalise_link($contact['url']))
);
}
}
/**
* @brief Get contact data for a given profile link
*
* The function looks at several places (contact table and gcontact table) for the contact
* It caches its result for the same script execution to prevent duplicate calls
*
* @param string $url The profile link
* @param int $uid User id
@ -204,35 +219,45 @@ function unmark_for_death($contact) {
* @return array Contact data
*/
function get_contact_details_by_url($url, $uid = -1, $default = array()) {
if ($uid == -1)
static $cache = array();
if ($uid == -1) {
$uid = local_user();
}
if (isset($cache[$url][$uid])) {
return $cache[$url][$uid];
}
// Fetch contact data from the contact table for the given user
$r = q("SELECT `id`, `id` AS `cid`, 0 AS `gid`, 0 AS `zid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`,
`keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `bd` AS `birthday`, `self`
$r = q("SELECT `id`, `id` AS `cid`, 0 AS `gid`, 0 AS `zid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, `xmpp`,
`keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, `self`
FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d",
dbesc(normalise_link($url)), intval($uid));
// Fetch the data from the contact table with "uid=0" (which is filled automatically)
if (!$r)
$r = q("SELECT `id`, 0 AS `cid`, `id` AS `zid`, 0 AS `gid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`,
`keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `bd` AS `birthday`, 0 AS `self`
$r = q("SELECT `id`, 0 AS `cid`, `id` AS `zid`, 0 AS `gid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, `xmpp`,
`keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, 0 AS `self`
FROM `contact` WHERE `nurl` = '%s' AND `uid` = 0",
dbesc(normalise_link($url)));
// Fetch the data from the gcontact table
if (!$r)
$r = q("SELECT 0 AS `id`, 0 AS `cid`, `id` AS `gid`, 0 AS `zid`, 0 AS `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`,
`keywords`, `gender`, `photo`, `photo` AS `thumb`, `photo` AS `micro`, `community` AS `forum`, 0 AS `prv`, `community`, `birthday`, 0 AS `self`
$r = q("SELECT 0 AS `id`, 0 AS `cid`, `id` AS `gid`, 0 AS `zid`, 0 AS `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, '' AS `xmpp`,
`keywords`, `gender`, `photo`, `photo` AS `thumb`, `photo` AS `micro`, `community` AS `forum`, 0 AS `prv`, `community`, `contact-type`, `birthday`, 0 AS `self`
FROM `gcontact` WHERE `nurl` = '%s'",
dbesc(normalise_link($url)));
if ($r) {
// If there is more than one entry we filter out the connector networks
if (count($r) > 1)
foreach ($r AS $id => $result)
if ($result["network"] == NETWORK_STATUSNET)
if (count($r) > 1) {
foreach ($r AS $id => $result) {
if ($result["network"] == NETWORK_STATUSNET) {
unset($r[$id]);
}
}
}
$profile = array_shift($r);
@ -251,31 +276,40 @@ function get_contact_details_by_url($url, $uid = -1, $default = array()) {
$profile["bd"] = $current_year."-".$month."-".$day;
$current = $current_year."-".$current_month."-".$current_day;
if ($profile["bd"] < $current)
if ($profile["bd"] < $current) {
$profile["bd"] = (++$current_year)."-".$month."-".$day;
} else
}
} else {
$profile["bd"] = "0000-00-00";
} else
}
} else {
$profile = $default;
}
if (($profile["photo"] == "") AND isset($default["photo"]))
if (($profile["photo"] == "") AND isset($default["photo"])) {
$profile["photo"] = $default["photo"];
}
if (($profile["name"] == "") AND isset($default["name"]))
if (($profile["name"] == "") AND isset($default["name"])) {
$profile["name"] = $default["name"];
}
if (($profile["network"] == "") AND isset($default["network"]))
if (($profile["network"] == "") AND isset($default["network"])) {
$profile["network"] = $default["network"];
}
if (($profile["thumb"] == "") AND isset($profile["photo"]))
if (($profile["thumb"] == "") AND isset($profile["photo"])) {
$profile["thumb"] = $profile["photo"];
}
if (($profile["micro"] == "") AND isset($profile["thumb"]))
if (($profile["micro"] == "") AND isset($profile["thumb"])) {
$profile["micro"] = $profile["thumb"];
}
if ((($profile["addr"] == "") OR ($profile["name"] == "")) AND ($profile["gid"] != 0) AND
in_array($profile["network"], array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS)))
in_array($profile["network"], array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS))) {
proc_run(PRIORITY_LOW, "include/update_gcontact.php", $profile["gid"]);
}
// Show contact details of Diaspora contacts only if connected
if (($profile["cid"] == 0) AND ($profile["network"] == NETWORK_DIASPORA)) {
@ -285,87 +319,93 @@ function get_contact_details_by_url($url, $uid = -1, $default = array()) {
$profile["birthday"] = "0000-00-00";
}
return($profile);
$cache[$url][$uid] = $profile;
return $profile;
}
if(! function_exists('contact_photo_menu')){
function contact_photo_menu($contact, $uid = 0) {
if (! function_exists('contact_photo_menu')) {
function contact_photo_menu($contact, $uid = 0)
{
$a = get_app();
$contact_url="";
$pm_url="";
$status_link="";
$photos_link="";
$posts_link="";
$contact_drop_link = "";
$poke_link="";
$contact_url = '';
$pm_url = '';
$status_link = '';
$photos_link = '';
$posts_link = '';
$contact_drop_link = '';
$poke_link = '';
if ($uid == 0)
if ($uid == 0) {
$uid = local_user();
}
if ($contact["uid"] != $uid) {
if ($contact['uid'] != $uid) {
if ($uid == 0) {
$profile_link = zrl($contact['url']);
$menu = Array('profile' => array(t("View Profile"), $profile_link, true));
$menu = Array('profile' => array(t('View Profile'), $profile_link, true));
return $menu;
}
$r = q("SELECT * FROM `contact` WHERE `nurl` = '%s' AND `network` = '%s' AND `uid` = %d",
dbesc($contact["nurl"]), dbesc($contact["network"]), intval($uid));
if ($r)
dbesc($contact['nurl']), dbesc($contact['network']), intval($uid));
if ($r) {
return contact_photo_menu($r[0], $uid);
else {
} else {
$profile_link = zrl($contact['url']);
$connlnk = 'follow/?url='.$contact['url'];
$menu = Array(
'profile' => array(t("View Profile"), $profile_link, true),
'follow' => array(t("Connect/Follow"), $connlnk, true)
);
$menu = array(
'profile' => array(t('View Profile'), $profile_link, true),
'follow' => array(t('Connect/Follow'), $connlnk, true)
);
return $menu;
}
}
$sparkle = false;
if($contact['network'] === NETWORK_DFRN) {
if ($contact['network'] === NETWORK_DFRN) {
$sparkle = true;
$profile_link = $a->get_baseurl() . '/redir/' . $contact['id'];
}
else
} else {
$profile_link = $contact['url'];
if($profile_link === 'mailbox')
$profile_link = '';
if($sparkle) {
$status_link = $profile_link . "?url=status";
$photos_link = $profile_link . "?url=photos";
$profile_link = $profile_link . "?url=profile";
}
if (in_array($contact["network"], array(NETWORK_DFRN, NETWORK_DIASPORA)))
$pm_url = $a->get_baseurl() . '/message/new/' . $contact['id'];
if ($profile_link === 'mailbox') {
$profile_link = '';
}
if ($contact["network"] == NETWORK_DFRN)
if ($sparkle) {
$status_link = $profile_link . '?url=status';
$photos_link = $profile_link . '?url=photos';
$profile_link = $profile_link . '?url=profile';
}
if (in_array($contact['network'], array(NETWORK_DFRN, NETWORK_DIASPORA))) {
$pm_url = $a->get_baseurl() . '/message/new/' . $contact['id'];
}
if ($contact['network'] == NETWORK_DFRN) {
$poke_link = $a->get_baseurl() . '/poke/?f=&c=' . $contact['id'];
}
$contact_url = $a->get_baseurl() . '/contacts/' . $contact['id'];
$posts_link = $a->get_baseurl() . "/contacts/" . $contact['id'] . '/posts';
$contact_drop_link = $a->get_baseurl() . "/contacts/" . $contact['id'] . '/drop?confirm=1';
$posts_link = $a->get_baseurl() . '/contacts/' . $contact['id'] . '/posts';
$contact_drop_link = $a->get_baseurl() . '/contacts/' . $contact['id'] . '/drop?confirm=1';
/**
* menu array:
* "name" => [ "Label", "link", (bool)Should the link opened in a new tab? ]
*/
$menu = Array(
$menu = array(
'status' => array(t("View Status"), $status_link, true),
'profile' => array(t("View Profile"), $profile_link, true),
'photos' => array(t("View Photos"), $photos_link,true),
'network' => array(t("Network Posts"), $posts_link,false),
'edit' => array(t("Edit Contact"), $contact_url, false),
'photos' => array(t("View Photos"), $photos_link, true),
'network' => array(t("Network Posts"), $posts_link, false),
'edit' => array(t("View Contact"), $contact_url, false),
'drop' => array(t("Drop Contact"), $contact_drop_link, false),
'pm' => array(t("Send PM"), $pm_url, false),
'poke' => array(t("Poke"), $poke_link, false),
@ -378,9 +418,11 @@ function contact_photo_menu($contact, $uid = 0) {
$menucondensed = array();
foreach ($menu AS $menuname=>$menuitem)
if ($menuitem[1] != "")
foreach ($menu AS $menuname => $menuitem) {
if ($menuitem[1] != '') {
$menucondensed[$menuname] = $menuitem;
}
}
return $menucondensed;
}}
@ -422,9 +464,20 @@ function contacts_not_grouped($uid,$start = 0,$count = 0) {
return $r;
}
function get_contact($url, $uid = 0) {
/**
* @brief Fetch the contact id for a given url and user
*
* @param string $url Contact URL
* @param integer $uid The user id for the contact
* @param boolean $no_update Don't update the contact
*
* @return integer Contact ID
*/
function get_contact($url, $uid = 0, $no_update = false) {
require_once("include/Scrape.php");
logger("Get contact data for url ".$url." and user ".$uid." - ".App::callstack(), LOGGER_DEBUG);;
$data = array();
$contactid = 0;
@ -454,8 +507,9 @@ function get_contact($url, $uid = 0) {
$update_photo = ($contact[0]['avatar-date'] < datetime_convert('','','now -7 days'));
//$update_photo = ($contact[0]['avatar-date'] < datetime_convert('','','now -12 hours'));
if (!$update_photo)
if (!$update_photo OR $no_update) {
return($contactid);
}
} elseif ($uid != 0)
return 0;
@ -481,9 +535,9 @@ function get_contact($url, $uid = 0) {
if ($contactid == 0) {
q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`,
`name`, `nick`, `photo`, `network`, `pubkey`, `rel`, `priority`,
`batch`, `request`, `confirm`, `poco`,
`batch`, `request`, `confirm`, `poco`, `name-date`, `uri-date`,
`writable`, `blocked`, `readonly`, `pending`)
VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', 1, 0, 0, 0)",
VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', 1, 0, 0, 0)",
intval($uid),
dbesc(datetime_convert()),
dbesc($data["url"]),
@ -502,7 +556,9 @@ function get_contact($url, $uid = 0) {
dbesc($data["batch"]),
dbesc($data["request"]),
dbesc($data["confirm"]),
dbesc($data["poco"])
dbesc($data["poco"]),
dbesc(datetime_convert()),
dbesc(datetime_convert())
);
$contact = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d ORDER BY `id` LIMIT 2",
@ -533,16 +589,27 @@ function get_contact($url, $uid = 0) {
update_contact_avatar($data["photo"],$uid,$contactid);
q("UPDATE `contact` SET `addr` = '%s', `alias` = '%s', `name` = '%s', `nick` = '%s',
`name-date` = '%s', `uri-date` = '%s' WHERE `id` = %d",
dbesc($data["addr"]),
dbesc($data["alias"]),
dbesc($data["name"]),
dbesc($data["nick"]),
dbesc(datetime_convert()),
dbesc(datetime_convert()),
intval($contactid)
);
$r = q("SELECT `addr`, `alias`, `name`, `nick` FROM `contact` WHERE `id` = %d", intval($contactid));
// This condition should always be true
if (!dbm::is_result($r))
return $contactid;
// Only update if there had something been changed
if (($data["addr"] != $r[0]["addr"]) OR
($data["alias"] != $r[0]["alias"]) OR
($data["name"] != $r[0]["name"]) OR
($data["nick"] != $r[0]["nick"]))
q("UPDATE `contact` SET `addr` = '%s', `alias` = '%s', `name` = '%s', `nick` = '%s',
`name-date` = '%s', `uri-date` = '%s' WHERE `id` = %d",
dbesc($data["addr"]),
dbesc($data["alias"]),
dbesc($data["name"]),
dbesc($data["nick"]),
dbesc(datetime_convert()),
dbesc(datetime_convert()),
intval($contactid)
);
return $contactid;
}
@ -599,57 +666,55 @@ function posts_from_gcontact($a, $gcontact_id) {
return $o;
}
/**
* @brief Returns posts from a given contact
* @brief Returns posts from a given contact url
*
* @param App $a argv application class
* @param int $contact_id contact
* @param string $contact_url Contact URL
*
* @return string posts in HTML
*/
function posts_from_contact($a, $contact_id) {
function posts_from_contact_url($a, $contact_url) {
require_once('include/conversation.php');
$r = q("SELECT `url` FROM `contact` WHERE `id` = %d", intval($contact_id));
if (!$r)
return false;
// There are no posts with "uid = 0" with connector networks
// This speeds up the query a lot
$r = q("SELECT `network`, `id` AS `author-id` FROM `contact`
WHERE `contact`.`nurl` = '%s' AND `contact`.`uid` = 0",
dbesc(normalise_link($contact_url)));
if (in_array($r[0]["network"], array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS, ""))) {
$sql = "(`item`.`uid` = 0 OR (`item`.`uid` = %d AND `item`.`private`))";
} else {
$sql = "`item`.`uid` = %d";
}
$contact = $r[0];
$author_id = intval($r[0]["author-id"]);
if(get_config('system', 'old_pager')) {
if (get_config('system', 'old_pager')) {
$r = q("SELECT COUNT(*) AS `total` FROM `item`
WHERE `item`.`uid` = %d AND `author-link` IN ('%s', '%s')",
intval(local_user()),
dbesc(str_replace("https://", "http://", $contact["url"])),
dbesc(str_replace("http://", "https://", $contact["url"])));
WHERE `author-id` = %d and $sql",
intval($author_id),
intval(local_user()));
$a->set_pager_total($r[0]['total']);
}
$r = q("SELECT `item`.`uri`, `item`.*, `item`.`id` AS `item_id`,
`author-name` AS `name`, `owner-avatar` AS `photo`,
`owner-link` AS `url`, `owner-avatar` AS `thumb`
FROM `item` FORCE INDEX (`uid_contactid_id`)
WHERE `item`.`uid` = %d AND `contact-id` = %d
AND `author-link` IN ('%s', '%s')
AND NOT `deleted` AND NOT `moderated` AND `visible`
ORDER BY `item`.`id` DESC LIMIT %d, %d",
$r = q(item_query()." AND `item`.`author-id` = %d AND ".$sql.
" ORDER BY `item`.`created` DESC LIMIT %d, %d",
intval($author_id),
intval(local_user()),
intval($contact_id),
dbesc(str_replace("https://", "http://", $contact["url"])),
dbesc(str_replace("http://", "https://", $contact["url"])),
intval($a->pager['start']),
intval($a->pager['itemspage'])
);
$o .= conversation($a,$r,'community',false);
$o = conversation($a,$r,'community',false);
if(!get_config('system', 'old_pager'))
if (!get_config('system', 'old_pager')) {
$o .= alt_pager($a,count($r));
else
} else {
$o .= paginate($a);
}
return $o;
}
@ -683,4 +748,50 @@ function formatted_location($profile) {
return $location;
}
/**
* @brief Returns the account type name
*
* The function can be called with either the user or the contact array
*
* @param array $contact contact or user array
*/
function account_type($contact) {
// There are several fields that indicate that the contact or user is a forum
// "page-flags" is a field in the user table,
// "forum" and "prv" are used in the contact table. They stand for PAGE_COMMUNITY and PAGE_PRVGROUP.
// "community" is used in the gcontact table and is true if the contact is PAGE_COMMUNITY or PAGE_PRVGROUP.
if((isset($contact['page-flags']) && (intval($contact['page-flags']) == PAGE_COMMUNITY))
|| (isset($contact['page-flags']) && (intval($contact['page-flags']) == PAGE_PRVGROUP))
|| (isset($contact['forum']) && intval($contact['forum']))
|| (isset($contact['prv']) && intval($contact['prv']))
|| (isset($contact['community']) && intval($contact['community'])))
$type = ACCOUNT_TYPE_COMMUNITY;
else
$type = ACCOUNT_TYPE_PERSON;
// The "contact-type" (contact table) and "account-type" (user table) are more general then the chaos from above.
if (isset($contact["contact-type"]))
$type = $contact["contact-type"];
if (isset($contact["account-type"]))
$type = $contact["account-type"];
switch($type) {
case ACCOUNT_TYPE_ORGANISATION:
$account_type = t("Organisation");
break;
case ACCOUNT_TYPE_NEWS:
$account_type = t('News');
break;
case ACCOUNT_TYPE_COMMUNITY:
$account_type = t("Forum");
break;
default:
$account_type = "";
break;
}
return $account_type;
}
?>

View file

@ -32,9 +32,9 @@ class Config {
public static function load($family) {
global $a;
$r = q("SELECT `v`, `k` FROM `config` WHERE `cat` = '%s'", dbesc($family));
if(count($r)) {
foreach($r as $rr) {
$r = q("SELECT `v`, `k` FROM `config` WHERE `cat` = '%s' ORDER BY `cat`, `k`, `id`", dbesc($family));
if (count($r)) {
foreach ($r as $rr) {
$k = $rr['k'];
if ($family === 'config') {
$a->config[$k] = $rr['v'];
@ -70,74 +70,38 @@ class Config {
* If true the config is loaded from the db and not from the cache (default: false)
* @return mixed Stored value or null if it does not exist
*/
public static function get($family, $key, $default_value=null, $refresh = false) {
public static function get($family, $key, $default_value = null, $refresh = false) {
global $a;
if(! $instore) {
if (!$refresh) {
// Looking if the whole family isn't set
if(isset($a->config[$family])) {
if($a->config[$family] === '!<unset>!') {
if (isset($a->config[$family])) {
if ($a->config[$family] === '!<unset>!') {
return $default_value;
}
}
if(isset($a->config[$family][$key])) {
if($a->config[$family][$key] === '!<unset>!') {
if (isset($a->config[$family][$key])) {
if ($a->config[$family][$key] === '!<unset>!') {
return $default_value;
}
return $a->config[$family][$key];
}
}
// If APC is enabled then fetch the data from there, else try XCache
/*if (function_exists("apc_fetch") AND function_exists("apc_exists"))
if (apc_exists($family."|".$key)) {
$val = apc_fetch($family."|".$key);
$a->config[$family][$key] = $val;
if ($val === '!<unset>!')
return false;
else
return $val;
}
elseif (function_exists("xcache_fetch") AND function_exists("xcache_isset"))
if (xcache_isset($family."|".$key)) {
$val = xcache_fetch($family."|".$key);
$a->config[$family][$key] = $val;
if ($val === '!<unset>!')
return false;
else
return $val;
}
*/
$ret = q("SELECT `v` FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
$ret = q("SELECT `v` FROM `config` WHERE `cat` = '%s' AND `k` = '%s' ORDER BY `id` DESC LIMIT 1",
dbesc($family),
dbesc($key)
);
if(count($ret)) {
if (count($ret)) {
// manage array value
$val = (preg_match("|^a:[0-9]+:{.*}$|s", $ret[0]['v'])?unserialize( $ret[0]['v']):$ret[0]['v']);
$a->config[$family][$key] = $val;
// If APC is enabled then store the data there, else try XCache
/*if (function_exists("apc_store"))
apc_store($family."|".$key, $val, 600);
elseif (function_exists("xcache_set"))
xcache_set($family."|".$key, $val, 600);*/
return $val;
}
else {
} else {
$a->config[$family][$key] = '!<unset>!';
// If APC is enabled then store the data there, else try XCache
/*if (function_exists("apc_store"))
apc_store($family."|".$key, '!<unset>!', 600);
elseif (function_exists("xcache_set"))
xcache_set($family."|".$key, '!<unset>!', 600);*/
}
return $default_value;
}
@ -158,48 +122,38 @@ class Config {
* The value to store
* @return mixed Stored $value or false if the database update failed
*/
public static function set($family,$key,$value) {
public static function set($family, $key, $value) {
global $a;
// If $a->config[$family] has been previously set to '!<unset>!', then
// $a->config[$family][$key] will evaluate to $a->config[$family][0], and
// $a->config[$family][$key] = $value will be equivalent to
// $a->config[$family][0] = $value[0] (this causes infuriating bugs),
// so unset the family before assigning a value to a family's key
if($a->config[$family] === '!<unset>!')
unset($a->config[$family]);
$stored = self::get($family, $key);
// manage array value
$dbvalue = (is_array($value)?serialize($value):$value);
$dbvalue = (is_bool($dbvalue) ? intval($dbvalue) : $dbvalue);
if(is_null(self::get($family,$key,null,true))) {
$a->config[$family][$key] = $value;
$ret = q("INSERT INTO `config` ( `cat`, `k`, `v` ) VALUES ( '%s', '%s', '%s' ) ",
dbesc($family),
dbesc($key),
dbesc($dbvalue)
);
if($ret)
return $value;
return $ret;
if ($stored == $value) {
return true;
}
$ret = q("UPDATE `config` SET `v` = '%s' WHERE `cat` = '%s' AND `k` = '%s'",
dbesc($dbvalue),
dbesc($family),
dbesc($key)
);
$a->config[$family][$key] = $value;
// If APC is enabled then store the data there, else try XCache
/*if (function_exists("apc_store"))
apc_store($family."|".$key, $value, 600);
elseif (function_exists("xcache_set"))
xcache_set($family."|".$key, $value, 600);*/
// manage array value
$dbvalue = (is_array($value) ? serialize($value) : $value);
$dbvalue = (is_bool($dbvalue) ? intval($dbvalue) : $dbvalue);
if($ret)
if (is_null($stored)) {
$ret = q("INSERT INTO `config` (`cat`, `k`, `v`) VALUES ('%s', '%s', '%s') ON DUPLICATE KEY UPDATE `v` = '%s'",
dbesc($family),
dbesc($key),
dbesc($dbvalue),
dbesc($dbvalue)
);
} else {
$ret = q("UPDATE `config` SET `v` = '%s' WHERE `cat` = '%s' AND `k` = '%s'",
dbesc($dbvalue),
dbesc($family),
dbesc($key)
);
}
if ($ret) {
return $value;
}
return $ret;
}
@ -215,20 +169,16 @@ class Config {
* The configuration key to delete
* @return mixed
*/
public static function delete($family,$key) {
public static function delete($family, $key) {
global $a;
if(x($a->config[$family],$key))
if (x($a->config[$family],$key)) {
unset($a->config[$family][$key]);
}
$ret = q("DELETE FROM `config` WHERE `cat` = '%s' AND `k` = '%s'",
dbesc($family),
dbesc($key)
);
// If APC is enabled then delete the data from there, else try XCache
/*if (function_exists("apc_delete"))
apc_delete($family."|".$key);
elseif (function_exists("xcache_unset"))
xcache_unset($family."|".$key);*/
return $ret;
}

View file

@ -27,14 +27,14 @@ class PConfig {
* The category of the configuration value
* @return void
*/
public static function load($uid,$family) {
public static function load($uid, $family) {
global $a;
$r = q("SELECT `v`,`k` FROM `pconfig` WHERE `cat` = '%s' AND `uid` = %d",
$r = q("SELECT `v`,`k` FROM `pconfig` WHERE `cat` = '%s' AND `uid` = %d ORDER BY `cat`, `k`, `id`",
dbesc($family),
intval($uid)
);
if(count($r)) {
foreach($r as $rr) {
if (count($r)) {
foreach ($r as $rr) {
$k = $rr['k'];
$a->config[$uid][$family][$k] = $rr['v'];
}
@ -67,71 +67,35 @@ class PConfig {
global $a;
if(! $instore) {
if (!$refresh) {
// Looking if the whole family isn't set
if(isset($a->config[$uid][$family])) {
if($a->config[$uid][$family] === '!<unset>!') {
if (isset($a->config[$uid][$family])) {
if ($a->config[$uid][$family] === '!<unset>!') {
return $default_value;
}
}
if(isset($a->config[$uid][$family][$key])) {
if($a->config[$uid][$family][$key] === '!<unset>!') {
if (isset($a->config[$uid][$family][$key])) {
if ($a->config[$uid][$family][$key] === '!<unset>!') {
return $default_value;
}
return $a->config[$uid][$family][$key];
}
}
// If APC is enabled then fetch the data from there, else try XCache
/*if (function_exists("apc_fetch") AND function_exists("apc_exists"))
if (apc_exists($uid."|".$family."|".$key)) {
$val = apc_fetch($uid."|".$family."|".$key);
$a->config[$uid][$family][$key] = $val;
if ($val === '!<unset>!')
return false;
else
return $val;
}
elseif (function_exists("xcache_get") AND function_exists("xcache_isset"))
if (xcache_isset($uid."|".$family."|".$key)) {
$val = xcache_get($uid."|".$family."|".$key);
$a->config[$uid][$family][$key] = $val;
if ($val === '!<unset>!')
return false;
else
return $val;
}*/
$ret = q("SELECT `v` FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
$ret = q("SELECT `v` FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' ORDER BY `id` DESC LIMIT 1",
intval($uid),
dbesc($family),
dbesc($key)
);
if(count($ret)) {
if (count($ret)) {
$val = (preg_match("|^a:[0-9]+:{.*}$|s", $ret[0]['v'])?unserialize( $ret[0]['v']):$ret[0]['v']);
$a->config[$uid][$family][$key] = $val;
// If APC is enabled then store the data there, else try XCache
/*if (function_exists("apc_store"))
apc_store($uid."|".$family."|".$key, $val, 600);
elseif (function_exists("xcache_set"))
xcache_set($uid."|".$family."|".$key, $val, 600);*/
return $val;
}
else {
} else {
$a->config[$uid][$family][$key] = '!<unset>!';
// If APC is enabled then store the data there, else try XCache
/*if (function_exists("apc_store"))
apc_store($uid."|".$family."|".$key, '!<unset>!', 600);
elseif (function_exists("xcache_set"))
xcache_set($uid."|".$family."|".$key, '!<unset>!', 600);*/
}
return $default_value;
}
@ -154,43 +118,41 @@ class PConfig {
* The value to store
* @return mixed Stored $value or false
*/
public static function set($uid,$family,$key,$value) {
public static function set($uid, $family, $key, $value) {
global $a;
// manage array value
$dbvalue = (is_array($value)?serialize($value):$value);
$stored = self::get($uid, $family, $key);
if(is_null(self::get($uid,$family,$key,null, true))) {
$a->config[$uid][$family][$key] = $value;
$ret = q("INSERT INTO `pconfig` ( `uid`, `cat`, `k`, `v` ) VALUES ( %d, '%s', '%s', '%s' ) ",
intval($uid),
dbesc($family),
dbesc($key),
dbesc($dbvalue)
);
if($ret)
return $value;
return $ret;
if ($stored == $value) {
return true;
}
$ret = q("UPDATE `pconfig` SET `v` = '%s' WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s'",
dbesc($dbvalue),
intval($uid),
dbesc($family),
dbesc($key)
);
// manage array value
$dbvalue = (is_array($value) ? serialize($value):$value);
$a->config[$uid][$family][$key] = $value;
// If APC is enabled then store the data there, else try XCache
/*if (function_exists("apc_store"))
apc_store($uid."|".$family."|".$key, $value, 600);
elseif (function_exists("xcache_set"))
xcache_set($uid."|".$family."|".$key, $value, 600);*/
if (is_null($stored)) {
$ret = q("INSERT INTO `pconfig` (`uid`, `cat`, `k`, `v`) VALUES (%d, '%s', '%s', '%s') ON DUPLICATE KEY UPDATE `v` = '%s'",
intval($uid),
dbesc($family),
dbesc($key),
dbesc($dbvalue),
dbesc($dbvalue)
);
} else {
$ret = q("UPDATE `pconfig` SET `v` = '%s' WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s'",
dbesc($dbvalue),
intval($uid),
dbesc($family),
dbesc($key)
);
}
if($ret)
if ($ret) {
return $value;
}
return $ret;
}
@ -210,13 +172,17 @@ class PConfig {
public static function delete($uid,$family,$key) {
global $a;
if(x($a->config[$uid][$family],$key))
if (x($a->config[$uid][$family], $key)) {
unset($a->config[$uid][$family][$key]);
}
$ret = q("DELETE FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s'",
intval($uid),
dbesc($family),
dbesc($key)
);
return $ret;
}
}

View file

@ -213,8 +213,9 @@ class NotificationsManager {
// Because we use different db tables for the notification query
// we have sometimes $it['unseen'] and sometimes $it['seen].
// So we will have to transform $it['unseen']
if($it['unseen'])
if (array_key_exists('unseen', $it)) {
$it['seen'] = ($it['unseen'] > 0 ? false : true);
}
// Depending on the identifier of the notification we need to use different defaults
switch ($ident) {
@ -224,16 +225,14 @@ class NotificationsManager {
$default_item_image = proxy_url($it['photo'], false, PROXY_SIZE_MICRO);
$default_item_text = strip_tags(bbcode($it['msg']));
$default_item_when = relative_date($it['date']);
$default_tpl = $tpl_notify;
break;
case 'home':
$default_item_label = 'comment';
$default_item_link = $this->a->get_baseurl(true).'/display/'.$it['pguid'];
$default_item_image = proxy_url($it['author-avatar'], false, PROXY_SIZE_MICRO);
$default_item_text = sprintf( t("%s commented on %s's post"), $it['author-name'], $it['pname']);
$default_item_text = sprintf(t("%s commented on %s's post"), $it['author-name'], $it['pname']);
$default_item_when = relative_date($it['created']);
$default_tpl = $tpl_item_comments;
break;
default:
@ -241,21 +240,20 @@ class NotificationsManager {
$default_item_link = $this->a->get_baseurl(true).'/display/'.$it['pguid'];
$default_item_image = proxy_url($it['author-avatar'], false, PROXY_SIZE_MICRO);
$default_item_text = (($it['id'] == $it['parent'])
? sprintf( t("%s created a new post"), $it['author-name'])
: sprintf( t("%s commented on %s's post"), $it['author-name'], $it['pname']));
? sprintf(t("%s created a new post"), $it['author-name'])
: sprintf(t("%s commented on %s's post"), $it['author-name'], $it['pname']));
$default_item_when = relative_date($it['created']);
$default_tpl = (($it['id'] == $it['parent']) ? $tpl_item_posts : $tpl_item_comments);
}
// Transform the different types of notification in an usable array
switch($it['verb']){
switch ($it['verb']){
case ACTIVITY_LIKE:
$notif = array(
'label' => 'like',
'link' => $this->a->get_baseurl(true).'/display/'.$it['pguid'],
'$image' => proxy_url($it['author-avatar'], false, PROXY_SIZE_MICRO),
'text' => sprintf( t("%s liked %s's post"), $it['author-name'], $it['pname']),
'image' => proxy_url($it['author-avatar'], false, PROXY_SIZE_MICRO),
'text' => sprintf(t("%s liked %s's post"), $it['author-name'], $it['pname']),
'when' => relative_date($it['created']),
'seen' => $it['seen']
);
@ -266,7 +264,7 @@ class NotificationsManager {
'label' => 'dislike',
'link' => $this->a->get_baseurl(true).'/display/'.$it['pguid'],
'image' => proxy_url($it['author-avatar'], false, PROXY_SIZE_MICRO),
'text' => sprintf( t("%s disliked %s's post"), $it['author-name'], $it['pname']),
'text' => sprintf(t("%s disliked %s's post"), $it['author-name'], $it['pname']),
'when' => relative_date($it['created']),
'seen' => $it['seen']
);
@ -277,7 +275,7 @@ class NotificationsManager {
'label' => 'attend',
'link' => $this->a->get_baseurl(true).'/display/'.$it['pguid'],
'image' => proxy_url($it['author-avatar'], false, PROXY_SIZE_MICRO),
'text' => sprintf( t("%s is attending %s's event"), $it['author-name'], $it['pname']),
'text' => sprintf(t("%s is attending %s's event"), $it['author-name'], $it['pname']),
'when' => relative_date($it['created']),
'seen' => $it['seen']
);
@ -299,7 +297,7 @@ class NotificationsManager {
'label' => 'attendmaybe',
'link' => $this->a->get_baseurl(true).'/display/'.$it['pguid'],
'image' => proxy_url($it['author-avatar'], false, PROXY_SIZE_MICRO),
'text' => sprintf( t("%s may attend %s's event"), $it['author-name'], $it['pname']),
'text' => sprintf(t("%s may attend %s's event"), $it['author-name'], $it['pname']),
'when' => relative_date($it['created']),
'seen' => $it['seen']
);
@ -314,7 +312,7 @@ class NotificationsManager {
'label' => 'friend',
'link' => $this->a->get_baseurl(true).'/display/'.$it['pguid'],
'image' => proxy_url($it['author-avatar'], false, PROXY_SIZE_MICRO),
'text' => sprintf( t("%s is now friends with %s"), $it['author-name'], $it['fname']),
'text' => sprintf(t("%s is now friends with %s"), $it['author-name'], $it['fname']),
'when' => relative_date($it['created']),
'seen' => $it['seen']
);
@ -554,7 +552,7 @@ class NotificationsManager {
$r = q("SELECT `item`.`id`,`item`.`parent`, `item`.`verb`, `item`.`author-name`, `item`.`unseen`,
`item`.`author-link`, `item`.`author-avatar`, `item`.`created`, `item`.`object` AS `object`,
`pitem`.`author-name` AS `pname`, `pitem`.`author-link` AS `plink`, `pitem`.`guid` AS `pguid`,
`pitem`.`author-name` AS `pname`, `pitem`.`author-link` AS `plink`, `pitem`.`guid` AS `pguid`
FROM `item` INNER JOIN `item` AS `pitem` ON `pitem`.`id`=`item`.`parent`
WHERE `item`.`visible` = 1
$sql_extra

552
include/ParseUrl.php Normal file
View file

@ -0,0 +1,552 @@
<?php
/**
* @file include/ParseUrl.php
* @brief Get informations about a given URL
*/
namespace Friendica;
use \Friendica\Core\Config;
require_once("include/network.php");
require_once("include/Photo.php");
require_once("include/oembed.php");
require_once("include/xml.php");
/**
* @brief Class with methods for extracting certain content from an url
*/
class ParseUrl {
/**
* @brief Search for chached embeddable data of an url otherwise fetch it
*
* @param type $url The url of the page which should be scraped
* @param type $no_guessing If true the parse doens't search for
* preview pictures
* @param type $do_oembed The false option is used by the function fetch_oembed()
* to avoid endless loops
*
* @return array which contains needed data for embedding
* string 'url' => The url of the parsed page
* string 'type' => Content type
* string 'title' => The title of the content
* string 'text' => The description for the content
* string 'image' => A preview image of the content (only available
* if $no_geuessing = false
* array'images' = Array of preview pictures
* string 'keywords' => The tags which belong to the content
*
* @see ParseUrl::getSiteinfo() for more information about scraping
* embeddable content
*/
public static function getSiteinfoCached($url, $no_guessing = false, $do_oembed = true) {
if ($url == "") {
return false;
}
$r = q("SELECT * FROM `parsed_url` WHERE `url` = '%s' AND `guessing` = %d AND `oembed` = %d",
dbesc(normalise_link($url)), intval(!$no_guessing), intval($do_oembed));
if ($r) {
$data = $r[0]["content"];
}
if (!is_null($data)) {
$data = unserialize($data);
return $data;
}
$data = self::getSiteinfo($url, $no_guessing, $do_oembed);
q("INSERT INTO `parsed_url` (`url`, `guessing`, `oembed`, `content`, `created`) VALUES ('%s', %d, %d, '%s', '%s')
ON DUPLICATE KEY UPDATE `content` = '%s', `created` = '%s'",
dbesc(normalise_link($url)), intval(!$no_guessing), intval($do_oembed),
dbesc(serialize($data)), dbesc(datetime_convert()),
dbesc(serialize($data)), dbesc(datetime_convert()));
return $data;
}
/**
* @brief Parse a page for embeddable content information
*
* This method parses to url for meta data which can be used to embed
* the content. If available it prioritizes Open Graph meta tags.
* If this is not available it uses the twitter cards meta tags.
* As fallback it uses standard html elements with meta informations
* like \<title\>Awesome Title\</title\> or
* \<meta name="description" content="An awesome description"\>
*
* @param type $url The url of the page which should be scraped
* @param type $no_guessing If true the parse doens't search for
* preview pictures
* @param type $do_oembed The false option is used by the function fetch_oembed()
* to avoid endless loops
* @param type $count Internal counter to avoid endless loops
*
* @return array which contains needed data for embedding
* string 'url' => The url of the parsed page
* string 'type' => Content type
* string 'title' => The title of the content
* string 'text' => The description for the content
* string 'image' => A preview image of the content (only available
* if $no_geuessing = false
* array'images' = Array of preview pictures
* string 'keywords' => The tags which belong to the content
*
* @todo https://developers.google.com/+/plugins/snippet/
* @verbatim
* <meta itemprop="name" content="Awesome title">
* <meta itemprop="description" content="An awesome description">
* <meta itemprop="image" content="http://maple.libertreeproject.org/images/tree-icon.png">
*
* <body itemscope itemtype="http://schema.org/Product">
* <h1 itemprop="name">Shiny Trinket</h1>
* <img itemprop="image" src="{image-url}" />
* <p itemprop="description">Shiny trinkets are shiny.</p>
* </body>
* @endverbatim
*/
public static function getSiteinfo($url, $no_guessing = false, $do_oembed = true, $count = 1) {
$a = get_app();
$siteinfo = array();
// Check if the URL does contain a scheme
$scheme = parse_url($url, PHP_URL_SCHEME);
if ($scheme == "") {
$url = "http://".trim($url, "/");
}
if ($count > 10) {
logger("parseurl_getsiteinfo: Endless loop detected for ".$url, LOGGER_DEBUG);
return($siteinfo);
}
$url = trim($url, "'");
$url = trim($url, '"');
$url = original_url($url);
$siteinfo["url"] = $url;
$siteinfo["type"] = "link";
$check_cert = Config::get("system", "verifyssl");
$stamp1 = microtime(true);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_NOBODY, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 3);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, (($check_cert) ? 2 : false));
$header = curl_exec($ch);
$curl_info = @curl_getinfo($ch);
$http_code = $curl_info["http_code"];
curl_close($ch);
$a->save_timestamp($stamp1, "network");
if ((($curl_info["http_code"] == "301") || ($curl_info["http_code"] == "302") || ($curl_info["http_code"] == "303") || ($curl_info["http_code"] == "307"))
&& (($curl_info["redirect_url"] != "") || ($curl_info["location"] != ""))) {
if ($curl_info["redirect_url"] != "") {
$siteinfo = self::getSiteinfo($curl_info["redirect_url"], $no_guessing, $do_oembed, ++$count);
} else {
$siteinfo = self::getSiteinfo($curl_info["location"], $no_guessing, $do_oembed, ++$count);
}
return($siteinfo);
}
// If the file is too large then exit
if ($curl_info["download_content_length"] > 1000000) {
return($siteinfo);
}
// If it isn't a HTML file then exit
if (($curl_info["content_type"] != "") && !strstr(strtolower($curl_info["content_type"]), "html")) {
return($siteinfo);
}
if ($do_oembed) {
$oembed_data = oembed_fetch_url($url);
if (!in_array($oembed_data->type, array("error", "rich"))) {
$siteinfo["type"] = $oembed_data->type;
}
if (($oembed_data->type == "link") && ($siteinfo["type"] != "photo")) {
if (isset($oembed_data->title)) {
$siteinfo["title"] = $oembed_data->title;
}
if (isset($oembed_data->description)) {
$siteinfo["text"] = trim($oembed_data->description);
}
if (isset($oembed_data->thumbnail_url)) {
$siteinfo["image"] = $oembed_data->thumbnail_url;
}
}
}
$stamp1 = microtime(true);
// Now fetch the body as well
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_NOBODY, 0);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, (($check_cert) ? 2 : false));
$header = curl_exec($ch);
$curl_info = @curl_getinfo($ch);
$http_code = $curl_info["http_code"];
curl_close($ch);
$a->save_timestamp($stamp1, "network");
// Fetch the first mentioned charset. Can be in body or header
$charset = "";
if (preg_match('/charset=(.*?)['."'".'"\s\n]/', $header, $matches)) {
$charset = trim(trim(trim(array_pop($matches)), ';,'));
}
if ($charset == "") {
$charset = "utf-8";
}
$pos = strpos($header, "\r\n\r\n");
if ($pos) {
$body = trim(substr($header, $pos));
} else {
$body = $header;
}
if (($charset != "") && (strtoupper($charset) != "UTF-8")) {
logger("parseurl_getsiteinfo: detected charset ".$charset, LOGGER_DEBUG);
//$body = mb_convert_encoding($body, "UTF-8", $charset);
$body = iconv($charset, "UTF-8//TRANSLIT", $body);
}
$body = mb_convert_encoding($body, 'HTML-ENTITIES', "UTF-8");
$doc = new \DOMDocument();
@$doc->loadHTML($body);
\xml::deleteNode($doc, "style");
\xml::deleteNode($doc, "script");
\xml::deleteNode($doc, "option");
\xml::deleteNode($doc, "h1");
\xml::deleteNode($doc, "h2");
\xml::deleteNode($doc, "h3");
\xml::deleteNode($doc, "h4");
\xml::deleteNode($doc, "h5");
\xml::deleteNode($doc, "h6");
\xml::deleteNode($doc, "ol");
\xml::deleteNode($doc, "ul");
$xpath = new \DomXPath($doc);
$list = $xpath->query("//meta[@content]");
foreach ($list as $node) {
$attr = array();
if ($node->attributes->length) {
foreach ($node->attributes as $attribute) {
$attr[$attribute->name] = $attribute->value;
}
}
if (@$attr["http-equiv"] == "refresh") {
$path = $attr["content"];
$pathinfo = explode(";", $path);
$content = "";
foreach ($pathinfo as $value) {
if (substr(strtolower($value), 0, 4) == "url=") {
$content = substr($value, 4);
}
}
if ($content != "") {
$siteinfo = self::getSiteinfo($content, $no_guessing, $do_oembed, ++$count);
return($siteinfo);
}
}
}
$list = $xpath->query("//title");
if ($list->length > 0) {
$siteinfo["title"] = $list->item(0)->nodeValue;
}
//$list = $xpath->query("head/meta[@name]");
$list = $xpath->query("//meta[@name]");
foreach ($list as $node) {
$attr = array();
if ($node->attributes->length) {
foreach ($node->attributes as $attribute) {
$attr[$attribute->name] = $attribute->value;
}
}
$attr["content"] = trim(html_entity_decode($attr["content"], ENT_QUOTES, "UTF-8"));
if ($attr["content"] != "") {
switch (strtolower($attr["name"])) {
case "fulltitle":
$siteinfo["title"] = $attr["content"];
break;
case "description":
$siteinfo["text"] = $attr["content"];
break;
case "thumbnail":
$siteinfo["image"] = $attr["content"];
break;
case "twitter:image":
$siteinfo["image"] = $attr["content"];
break;
case "twitter:image:src":
$siteinfo["image"] = $attr["content"];
break;
case "twitter:card":
if (($siteinfo["type"] == "") || ($attr["content"] == "photo")) {
$siteinfo["type"] = $attr["content"];
}
break;
case "twitter:description":
$siteinfo["text"] = $attr["content"];
break;
case "twitter:title":
$siteinfo["title"] = $attr["content"];
break;
case "dc.title":
$siteinfo["title"] = $attr["content"];
break;
case "dc.description":
$siteinfo["text"] = $attr["content"];
break;
case "keywords":
$keywords = explode(",", $attr["content"]);
break;
case "news_keywords":
$keywords = explode(",", $attr["content"]);
break;
}
}
if ($siteinfo["type"] == "summary") {
$siteinfo["type"] = "link";
}
}
if (isset($keywords)) {
$siteinfo["keywords"] = array();
foreach ($keywords as $keyword) {
if (!in_array(trim($keyword), $siteinfo["keywords"])) {
$siteinfo["keywords"][] = trim($keyword);
}
}
}
//$list = $xpath->query("head/meta[@property]");
$list = $xpath->query("//meta[@property]");
foreach ($list as $node) {
$attr = array();
if ($node->attributes->length) {
foreach ($node->attributes as $attribute) {
$attr[$attribute->name] = $attribute->value;
}
}
$attr["content"] = trim(html_entity_decode($attr["content"], ENT_QUOTES, "UTF-8"));
if ($attr["content"] != "") {
switch (strtolower($attr["property"])) {
case "og:image":
$siteinfo["image"] = $attr["content"];
break;
case "og:title":
$siteinfo["title"] = $attr["content"];
break;
case "og:description":
$siteinfo["text"] = $attr["content"];
break;
}
}
}
if ((@$siteinfo["image"] == "") && !$no_guessing) {
$list = $xpath->query("//img[@src]");
foreach ($list as $node) {
$attr = array();
if ($node->attributes->length) {
foreach ($node->attributes as $attribute) {
$attr[$attribute->name] = $attribute->value;
}
}
$src = self::completeUrl($attr["src"], $url);
$photodata = get_photo_info($src);
if (($photodata) && ($photodata[0] > 150) && ($photodata[1] > 150)) {
if ($photodata[0] > 300) {
$photodata[1] = round($photodata[1] * (300 / $photodata[0]));
$photodata[0] = 300;
}
if ($photodata[1] > 300) {
$photodata[0] = round($photodata[0] * (300 / $photodata[1]));
$photodata[1] = 300;
}
$siteinfo["images"][] = array("src" => $src,
"width" => $photodata[0],
"height" => $photodata[1]);
}
}
} elseif ($siteinfo["image"] != "") {
$src = self::completeUrl($siteinfo["image"], $url);
unset($siteinfo["image"]);
$photodata = get_photo_info($src);
if (($photodata) && ($photodata[0] > 10) && ($photodata[1] > 10)) {
$siteinfo["images"][] = array("src" => $src,
"width" => $photodata[0],
"height" => $photodata[1]);
}
}
if ((@$siteinfo["text"] == "") && (@$siteinfo["title"] != "") && !$no_guessing) {
$text = "";
$list = $xpath->query("//div[@class='article']");
foreach ($list as $node) {
if (strlen($node->nodeValue) > 40) {
$text .= " ".trim($node->nodeValue);
}
}
if ($text == "") {
$list = $xpath->query("//div[@class='content']");
foreach ($list as $node) {
if (strlen($node->nodeValue) > 40) {
$text .= " ".trim($node->nodeValue);
}
}
}
// If none text was found then take the paragraph content
if ($text == "") {
$list = $xpath->query("//p");
foreach ($list as $node) {
if (strlen($node->nodeValue) > 40) {
$text .= " ".trim($node->nodeValue);
}
}
}
if ($text != "") {
$text = trim(str_replace(array("\n", "\r"), array(" ", " "), $text));
while (strpos($text, " ")) {
$text = trim(str_replace(" ", " ", $text));
}
$siteinfo["text"] = trim(html_entity_decode(substr($text, 0, 350), ENT_QUOTES, "UTF-8").'...');
}
}
logger("parseurl_getsiteinfo: Siteinfo for ".$url." ".print_r($siteinfo, true), LOGGER_DEBUG);
call_hooks("getsiteinfo", $siteinfo);
return($siteinfo);
}
/**
* @brief Convert tags from CSV to an array
*
* @param string $string Tags
* @return array with formatted Hashtags
*/
public static function convertTagsToArray($string) {
$arr_tags = str_getcsv($string);
if (count($arr_tags)) {
// add the # sign to every tag
array_walk($arr_tags, array("self", "arrAddHashes"));
return $arr_tags;
}
}
/**
* @brief Add a hasht sign to a string
*
* This method is used as callback function
*
* @param string $tag The pure tag name
* @param int $k Counter for internal use
*/
private static function arrAddHashes(&$tag, $k) {
$tag = "#" . $tag;
}
/**
* @brief Add a scheme to an url
*
* The src attribute of some html elements (e.g. images)
* can miss the scheme so we need to add the correct
* scheme
*
* @param string $url The url which possibly does have
* a missing scheme (a link to an image)
* @param string $scheme The url with a correct scheme
* (e.g. the url from the webpage which does contain the image)
*
* @return string The url with a scheme
*/
private static function completeUrl($url, $scheme) {
$urlarr = parse_url($url);
// If the url does allready have an scheme
// we can stop the process here
if (isset($urlarr["scheme"])) {
return($url);
}
$schemearr = parse_url($scheme);
$complete = $schemearr["scheme"]."://".$schemearr["host"];
if (@$schemearr["port"] != "") {
$complete .= ":".$schemearr["port"];
}
if (strpos($urlarr["path"],"/") !== 0) {
$complete .= "/";
}
$complete .= $urlarr["path"];
if (@$urlarr["query"] != "") {
$complete .= "?".$urlarr["query"];
}
if (@$urlarr["fragment"] != "") {
$complete .= "#".$urlarr["fragment"];
}
return($complete);
}
}

File diff suppressed because it is too large Load diff

View file

@ -118,18 +118,16 @@ class Probe {
*/
public static function webfinger_dfrn($webbie, &$hcard) {
if (!strstr($webbie, '@'))
return $webbie;
$profile_link = '';
$links = self::webfinger($webbie);
$links = self::lrdd($webbie);
logger('webfinger_dfrn: '.$webbie.':'.print_r($links,true), LOGGER_DATA);
if (count($links)) {
foreach ($links as $link) {
if ($link['@attributes']['rel'] === NAMESPACE_DFRN)
$profile_link = $link['@attributes']['href'];
if ($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB)
if (($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB) AND ($profile_link == ""))
$profile_link = 'stat:'.$link['@attributes']['template'];
if ($link['@attributes']['rel'] === 'http://microformats.org/profile/hcard')
$hcard = $link['@attributes']['href'];
@ -180,6 +178,11 @@ class Probe {
$path = str_replace('{uri}', urlencode($uri), $link);
$webfinger = self::webfinger($path);
if (!$webfinger AND (strstr($uri, "@"))) {
$path = str_replace('{uri}', urlencode("acct:".$uri), $link);
$webfinger = self::webfinger($path);
}
}
if (!is_array($webfinger["links"]))
@ -214,7 +217,6 @@ class Probe {
if ($cache) {
$result = Cache::get("probe_url:".$network.":".$uri);
if (!is_null($result)) {
$result = unserialize($result);
return $result;
}
}
@ -254,7 +256,7 @@ class Probe {
// Only store into the cache if the value seems to be valid
if (!in_array($data['network'], array(NETWORK_PHANTOM, NETWORK_MAIL))) {
Cache::set("probe_url:".$network.":".$uri,serialize($data), CACHE_DAY);
Cache::set("probe_url:".$network.":".$uri, $data, CACHE_DAY);
/// @todo temporary fix - we need a real contact update function that updates only changing fields
/// The biggest problem is the avatar picture that could have a reduced image size.
@ -310,6 +312,7 @@ class Probe {
return array("network" => NETWORK_TWITTER);
$lrdd = self::xrd($host);
if (!$lrdd)
return self::mail($uri, $uid);
@ -356,6 +359,12 @@ class Probe {
$path = str_replace('{uri}', urlencode($addr), $link);
$webfinger = self::webfinger($path);
// Mastodon needs to have it with "acct:"
if (!$webfinger) {
$path = str_replace('{uri}', urlencode("acct:".$addr), $link);
$webfinger = self::webfinger($path);
}
// If webfinger wasn't successful then try it with the URL - possibly in the format https://...
if (!$webfinger AND ($uri != $addr)) {
$path = str_replace('{uri}', urlencode($uri), $link);
@ -560,6 +569,8 @@ class Probe {
$data = array();
logger("Check profile ".$profile, LOGGER_DEBUG);
// Fetch data via noscrape - this is faster
$noscrape = str_replace(array("/hcard/", "/profile/"), "/noscrape/", $profile);
$data = self::poll_noscrape($noscrape, $data);
@ -582,6 +593,8 @@ class Probe {
$prof_data["fn"] = $data["name"];
$prof_data["key"] = $data["pubkey"];
logger("Result for profile ".$profile.": ".print_r($prof_data, true), LOGGER_DEBUG);
return $prof_data;
}
@ -651,8 +664,12 @@ class Probe {
*/
private function poll_hcard($hcard, $data, $dfrn = false) {
$content = fetch_url($hcard);
if (!$content)
return false;
$doc = new DOMDocument();
if (!@$doc->loadHTMLFile($hcard))
if (!@$doc->loadHTML($content))
return false;
$xpath = new DomXPath($doc);
@ -661,40 +678,39 @@ class Probe {
if (!is_object($vcards))
return false;
if ($vcards->length == 0)
return false;
if ($vcards->length > 0) {
$vcard = $vcards->item(0);
$vcard = $vcards->item(0);
// We have to discard the guid from the hcard in favour of the guid from lrdd
// Reason: Hubzilla doesn't use the value "uid" in the hcard like Diaspora does.
$search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' uid ')]", $vcard); // */
if (($search->length > 0) AND ($data["guid"] == ""))
$data["guid"] = $search->item(0)->nodeValue;
// We have to discard the guid from the hcard in favour of the guid from lrdd
// Reason: Hubzilla doesn't use the value "uid" in the hcard like Diaspora does.
$search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' uid ')]", $vcard); // */
if (($search->length > 0) AND ($data["guid"] == ""))
$data["guid"] = $search->item(0)->nodeValue;
$search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' nickname ')]", $vcard); // */
if ($search->length > 0)
$data["nick"] = $search->item(0)->nodeValue;
$search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' nickname ')]", $vcard); // */
if ($search->length > 0)
$data["nick"] = $search->item(0)->nodeValue;
$search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' fn ')]", $vcard); // */
if ($search->length > 0)
$data["name"] = $search->item(0)->nodeValue;
$search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' fn ')]", $vcard); // */
if ($search->length > 0)
$data["name"] = $search->item(0)->nodeValue;
$search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' searchable ')]", $vcard); // */
if ($search->length > 0)
$data["searchable"] = $search->item(0)->nodeValue;
$search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' searchable ')]", $vcard); // */
if ($search->length > 0)
$data["searchable"] = $search->item(0)->nodeValue;
$search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' key ')]", $vcard); // */
if ($search->length > 0) {
$data["pubkey"] = $search->item(0)->nodeValue;
if (strstr($data["pubkey"], 'RSA '))
$data["pubkey"] = rsatopem($data["pubkey"]);
}
$search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' key ')]", $vcard); // */
if ($search->length > 0) {
$data["pubkey"] = $search->item(0)->nodeValue;
if (strstr($data["pubkey"], 'RSA '))
$data["pubkey"] = rsatopem($data["pubkey"]);
$search = $xpath->query("//*[@id='pod_location']", $vcard); // */
if ($search->length > 0)
$data["baseurl"] = trim($search->item(0)->nodeValue, "/");
}
$search = $xpath->query("//*[@id='pod_location']", $vcard); // */
if ($search->length > 0)
$data["baseurl"] = trim($search->item(0)->nodeValue, "/");
$avatar = array();
$photos = $xpath->query("//*[contains(concat(' ', @class, ' '), ' photo ') or contains(concat(' ', @class, ' '), ' avatar ')]", $vcard); // */
foreach ($photos AS $photo) {
@ -815,6 +831,9 @@ class Probe {
if (strstr($alias, "@"))
$data["addr"] = str_replace('acct:', '', $alias);
if (is_string($webfinger["subject"]) AND strstr($webfinger["subject"], "@"))
$data["addr"] = str_replace('acct:', '', $webfinger["subject"]);
$pubkey = "";
foreach ($webfinger["links"] AS $link) {
if (($link["rel"] == "http://webfinger.net/rel/profile-page") AND
@ -832,7 +851,7 @@ class Probe {
$pubkey = substr($pubkey, strpos($pubkey, ',') + 1);
else
$pubkey = substr($pubkey, 5);
} else
} elseif (normalise_link($pubkey) == 'http://')
$pubkey = fetch_url($pubkey);
$key = explode(".", $pubkey);

View file

@ -280,6 +280,44 @@
$duration = (float)(microtime(true)-$stamp);
logger("API call duration: ".round($duration, 2)."\t".$a->query_string, LOGGER_DEBUG);
if (get_config("system", "profiler")) {
$duration = microtime(true)-$a->performance["start"];
logger(parse_url($a->query_string, PHP_URL_PATH).": ".sprintf("Database: %s/%s, Network: %s, I/O: %s, Other: %s, Total: %s",
round($a->performance["database"] - $a->performance["database_write"], 3),
round($a->performance["database_write"], 3),
round($a->performance["network"], 2),
round($a->performance["file"], 2),
round($duration - ($a->performance["database"] + $a->performance["network"]
+ $a->performance["file"]), 2),
round($duration, 2)),
LOGGER_DEBUG);
if (get_config("rendertime", "callstack")) {
$o = "Database Read:\n";
foreach ($a->callstack["database"] AS $func => $time) {
$time = round($time, 3);
if ($time > 0)
$o .= $func.": ".$time."\n";
}
$o .= "\nDatabase Write:\n";
foreach ($a->callstack["database_write"] AS $func => $time) {
$time = round($time, 3);
if ($time > 0)
$o .= $func.": ".$time."\n";
}
$o .= "\nNetwork:\n";
foreach ($a->callstack["network"] AS $func => $time) {
$time = round($time, 3);
if ($time > 0)
$o .= $func.": ".$time."\n";
}
logger($o, LOGGER_DEBUG);
}
}
if ($r===false) {
// api function returned false withour throw an
// exception. This should not happend, throw a 500
@ -391,7 +429,7 @@
* Contact url or False if contact id is unknown
*/
function api_unique_id_to_url($id){
$r = q("SELECT `url` FROM `gcontact` WHERE `id`=%d LIMIT 1",
$r = q("SELECT `url` FROM `contact` WHERE `uid` = 0 AND `id` = %d LIMIT 1",
intval($id));
if ($r)
return ($r[0]["url"]);
@ -423,7 +461,7 @@
if (api_user()!==false) $extra_query .= "AND `contact`.`uid`=".intval(api_user());
}
// Searching for unique contact id
// Searching for contact id with uid = 0
if(!is_null($contact_id) AND (intval($contact_id) != 0)){
$user = dbesc(api_unique_id_to_url($contact_id));
@ -496,14 +534,16 @@
// Selecting the id by priority, friendica first
api_best_nickname($uinfo);
// if the contact wasn't found, fetch it from the unique contacts
// if the contact wasn't found, fetch it from the contacts with uid = 0
if (count($uinfo)==0) {
$r = array();
if ($url != "")
$r = q("SELECT * FROM `gcontact` WHERE `nurl`='%s' LIMIT 1", dbesc(normalise_link($url)));
$r = q("SELECT * FROM `contact` WHERE `uid` = 0 AND `nurl` = '%s' LIMIT 1", dbesc(normalise_link($url)));
if ($r) {
$network_name = network_to_name($r[0]['network'], $r[0]['url']);
// If no nick where given, extract it from the address
if (($r[0]['nick'] == "") OR ($r[0]['name'] == $r[0]['nick']))
$r[0]['nick'] = api_get_nick($r[0]["url"]);
@ -513,8 +553,10 @@
'id_str' => (string) $r[0]["id"],
'name' => $r[0]["name"],
'screen_name' => (($r[0]['nick']) ? $r[0]['nick'] : $r[0]['name']),
'location' => $r[0]["location"],
'location' => ($r[0]["location"] != "") ? $r[0]["location"] : $network_name,
'description' => $r[0]["about"],
'profile_image_url' => $r[0]["micro"],
'profile_image_url_https' => $r[0]["micro"],
'url' => $r[0]["url"],
'protected' => false,
'followers_count' => 0,
@ -531,16 +573,13 @@
'contributors_enabled' => false,
'is_translator' => false,
'is_translation_enabled' => false,
'profile_image_url' => $r[0]["photo"],
'profile_image_url_https' => $r[0]["photo"],
'following' => false,
'follow_request_sent' => false,
'notifications' => false,
'statusnet_blocking' => false,
'notifications' => false,
'statusnet_profile_url' => $r[0]["url"],
'uid' => 0,
'cid' => get_contact($r[0]["url"], api_user()),
'cid' => get_contact($r[0]["url"], api_user(), true),
'self' => 0,
'network' => $r[0]["network"],
);
@ -563,28 +602,28 @@
intval(api_user())
);
//AND `allow_cid`='' AND `allow_gid`='' AND `deny_cid`='' AND `deny_gid`=''",
// Counting is deactivated by now, due to performance issues
// count public wall messages
$r = q("SELECT count(*) as `count` FROM `item`
WHERE `uid` = %d
AND `type`='wall'",
intval($uinfo[0]['uid'])
);
$countitms = $r[0]['count'];
//$r = q("SELECT COUNT(*) as `count` FROM `item` WHERE `uid` = %d AND `wall`",
// intval($uinfo[0]['uid'])
//);
//$countitms = $r[0]['count'];
$countitms = 0;
} else {
// Counting is deactivated by now, due to performance issues
//$r = q("SELECT count(*) as `count` FROM `item`
// WHERE `contact-id` = %d",
// intval($uinfo[0]['id'])
//);
//$countitms = $r[0]['count'];
$countitms = 0;
}
else {
//AND `allow_cid`='' AND `allow_gid`='' AND `deny_cid`='' AND `deny_gid`=''",
$r = q("SELECT count(*) as `count` FROM `item`
WHERE `contact-id` = %d",
intval($uinfo[0]['id'])
);
$countitms = $r[0]['count'];
}
/*
// Counting is deactivated by now, due to performance issues
// count friends
$r = q("SELECT count(*) as `count` FROM `contact`
WHERE `uid` = %d AND `rel` IN ( %d, %d )
AND `self`=0 AND `blocked`=0 AND `pending`=0 AND `hidden`=0",
AND `self`=0 AND NOT `blocked` AND `hidden`=0",
intval($uinfo[0]['uid']),
intval(CONTACT_IS_SHARING),
intval(CONTACT_IS_FRIEND)
@ -593,7 +632,7 @@
$r = q("SELECT count(*) as `count` FROM `contact`
WHERE `uid` = %d AND `rel` IN ( %d, %d )
AND `self`=0 AND `blocked`=0 AND `pending`=0 AND `hidden`=0",
AND `self`=0 AND NOT `blocked` AND `hidden`=0",
intval($uinfo[0]['uid']),
intval(CONTACT_IS_FOLLOWER),
intval(CONTACT_IS_FRIEND)
@ -611,6 +650,10 @@
$countfollowers = 0;
$starred = 0;
}
*/
$countfriends = 0;
$countfollowers = 0;
$starred = 0;
// Add a nick if it isn't present there
if (($uinfo[0]['nick'] == "") OR ($uinfo[0]['name'] == $uinfo[0]['nick'])) {
@ -619,12 +662,11 @@
$network_name = network_to_name($uinfo[0]['network'], $uinfo[0]['url']);
$gcontact_id = get_gcontact_id(array("url" => $uinfo[0]['url'], "network" => $uinfo[0]['network'],
"photo" => $uinfo[0]['micro'], "name" => $uinfo[0]['name']));
$pcontact_id = get_contact($uinfo[0]['url'], 0, true);
$ret = Array(
'id' => intval($gcontact_id),
'id_str' => (string) intval($gcontact_id),
'id' => intval($pcontact_id),
'id_str' => (string) intval($pcontact_id),
'name' => (($uinfo[0]['name']) ? $uinfo[0]['name'] : $uinfo[0]['nick']),
'screen_name' => (($uinfo[0]['nick']) ? $uinfo[0]['nick'] : $uinfo[0]['name']),
'location' => ($usr) ? $usr[0]['default-location'] : $network_name,
@ -635,13 +677,20 @@
'protected' => false,
'followers_count' => intval($countfollowers),
'friends_count' => intval($countfriends),
'listed_count' => 0,
'created_at' => api_date($uinfo[0]['created']),
'favourites_count' => intval($starred),
'utc_offset' => "0",
'time_zone' => 'UTC',
'statuses_count' => intval($countitms),
'following' => (($uinfo[0]['rel'] == CONTACT_IS_FOLLOWER) OR ($uinfo[0]['rel'] == CONTACT_IS_FRIEND)),
'geo_enabled' => false,
'verified' => true,
'statuses_count' => intval($countitms),
'lang' => '',
'contributors_enabled' => false,
'is_translator' => false,
'is_translation_enabled' => false,
'following' => (($uinfo[0]['rel'] == CONTACT_IS_FOLLOWER) OR ($uinfo[0]['rel'] == CONTACT_IS_FRIEND)),
'follow_request_sent' => false,
'statusnet_blocking' => false,
'notifications' => false,
//'statusnet_profile_url' => App::get_baseurl()."/contacts/".$uinfo[0]['cid'],
@ -665,21 +714,15 @@
*/
function api_item_get_user(&$a, $item) {
// Make sure that there is an entry in the global contacts for author and owner
get_gcontact_id(array("url" => $item['author-link'], "network" => $item['network'],
"photo" => $item['author-avatar'], "name" => $item['author-name']));
$status_user = api_get_user($a, $item["author-link"]);
get_gcontact_id(array("url" => $item['owner-link'], "network" => $item['network'],
"photo" => $item['owner-avatar'], "name" => $item['owner-name']));
$status_user = api_get_user($a,$item["author-link"]);
$status_user["protected"] = (($item["allow_cid"] != "") OR
($item["allow_gid"] != "") OR
($item["deny_cid"] != "") OR
($item["deny_gid"] != "") OR
$item["private"]);
$owner_user = api_get_user($a,$item["owner-link"]);
$owner_user = api_get_user($a, $item["owner-link"]);
return (array($status_user, $owner_user));
}
@ -1108,12 +1151,11 @@
$privacy_sql = "";
// get last public wall message
$lastwall = q("SELECT `item`.*, `i`.`contact-id` as `reply_uid`, `i`.`author-link` AS `item-author`
FROM `item`, `item` as `i`
$lastwall = q("SELECT `item`.*
FROM `item`
WHERE `item`.`contact-id` = %d AND `item`.`uid` = %d
AND ((`item`.`author-link` IN ('%s', '%s')) OR (`item`.`owner-link` IN ('%s', '%s')))
AND `i`.`id` = `item`.`parent`
AND `item`.`type`!='activity' $privacy_sql
AND `item`.`type` != 'activity' $privacy_sql
ORDER BY `item`.`id` DESC
LIMIT 1",
intval($user_info['cid']),
@ -1127,37 +1169,7 @@
if (count($lastwall)>0){
$lastwall = $lastwall[0];
$in_reply_to_status_id = NULL;
$in_reply_to_user_id = NULL;
$in_reply_to_status_id_str = NULL;
$in_reply_to_user_id_str = NULL;
$in_reply_to_screen_name = NULL;
if (intval($lastwall['parent']) != intval($lastwall['id'])) {
$in_reply_to_status_id= intval($lastwall['parent']);
$in_reply_to_status_id_str = (string) intval($lastwall['parent']);
$r = q("SELECT * FROM `gcontact` WHERE `nurl` = '%s'", dbesc(normalise_link($lastwall['item-author'])));
if ($r) {
if ($r[0]['nick'] == "")
$r[0]['nick'] = api_get_nick($r[0]["url"]);
$in_reply_to_screen_name = (($r[0]['nick']) ? $r[0]['nick'] : $r[0]['name']);
$in_reply_to_user_id = intval($r[0]['id']);
$in_reply_to_user_id_str = (string) intval($r[0]['id']);
}
}
// There seems to be situation, where both fields are identical:
// https://github.com/friendica/friendica/issues/1010
// This is a bugfix for that.
if (intval($in_reply_to_status_id) == intval($lastwall['id'])) {
logger('api_status_show: this message should never appear: id: '.$lastwall['id'].' similar to reply-to: '.$in_reply_to_status_id, LOGGER_DEBUG);
$in_reply_to_status_id = NULL;
$in_reply_to_user_id = NULL;
$in_reply_to_status_id_str = NULL;
$in_reply_to_user_id_str = NULL;
$in_reply_to_screen_name = NULL;
}
$in_reply_to = api_in_reply_to($lastwall);
$converted = api_convert_item($lastwall);
@ -1173,11 +1185,11 @@
'text' => $converted["text"],
'source' => (($lastwall['app']) ? $lastwall['app'] : 'web'),
'truncated' => false,
'in_reply_to_status_id' => $in_reply_to_status_id,
'in_reply_to_status_id_str' => $in_reply_to_status_id_str,
'in_reply_to_user_id' => $in_reply_to_user_id,
'in_reply_to_user_id_str' => $in_reply_to_user_id_str,
'in_reply_to_screen_name' => $in_reply_to_screen_name,
'in_reply_to_status_id' => $in_reply_to['status_id'],
'in_reply_to_status_id_str' => $in_reply_to['status_id_str'],
'in_reply_to_user_id' => $in_reply_to['user_id'],
'in_reply_to_user_id_str' => $in_reply_to['user_id_str'],
'in_reply_to_screen_name' => $in_reply_to['screen_name'],
'user' => $user_info,
$geo => NULL,
'coordinates' => "",
@ -1254,29 +1266,7 @@
if (count($lastwall)>0){
$lastwall = $lastwall[0];
$in_reply_to_status_id = NULL;
$in_reply_to_user_id = NULL;
$in_reply_to_status_id_str = NULL;
$in_reply_to_user_id_str = NULL;
$in_reply_to_screen_name = NULL;
if ($lastwall['parent']!=$lastwall['id']) {
$reply = q("SELECT `item`.`id`, `item`.`contact-id` as `reply_uid`, `contact`.`nick` as `reply_author`, `item`.`author-link` AS `item-author`
FROM `item`,`contact` WHERE `contact`.`id`=`item`.`contact-id` AND `item`.`id` = %d", intval($lastwall['parent']));
if (count($reply)>0) {
$in_reply_to_status_id = intval($lastwall['parent']);
$in_reply_to_status_id_str = (string) intval($lastwall['parent']);
$r = q("SELECT * FROM `gcontact` WHERE `nurl` = '%s'", dbesc(normalise_link($reply[0]['item-author'])));
if ($r) {
if ($r[0]['nick'] == "")
$r[0]['nick'] = api_get_nick($r[0]["url"]);
$in_reply_to_screen_name = (($r[0]['nick']) ? $r[0]['nick'] : $r[0]['name']);
$in_reply_to_user_id = intval($r[0]['id']);
$in_reply_to_user_id_str = (string) intval($r[0]['id']);
}
}
}
$in_reply_to = api_in_reply_to($lastwall);
$converted = api_convert_item($lastwall);
@ -1289,14 +1279,14 @@
'text' => $converted["text"],
'truncated' => false,
'created_at' => api_date($lastwall['created']),
'in_reply_to_status_id' => $in_reply_to_status_id,
'in_reply_to_status_id_str' => $in_reply_to_status_id_str,
'in_reply_to_status_id' => $in_reply_to['status_id'],
'in_reply_to_status_id_str' => $in_reply_to['status_id_str'],
'source' => (($lastwall['app']) ? $lastwall['app'] : 'web'),
'id' => intval($lastwall['contact-id']),
'id_str' => (string) $lastwall['contact-id'],
'in_reply_to_user_id' => $in_reply_to_user_id,
'in_reply_to_user_id_str' => $in_reply_to_user_id_str,
'in_reply_to_screen_name' => $in_reply_to_screen_name,
'in_reply_to_user_id' => $in_reply_to['user_id'],
'in_reply_to_user_id_str' => $in_reply_to['user_id_str'],
'in_reply_to_screen_name' => $in_reply_to['screen_name'],
$geo => NULL,
'favorited' => $lastwall['starred'] ? true : false,
'statusnet_html' => $converted["html"],
@ -1335,9 +1325,9 @@
$userlist = array();
if (isset($_GET["q"])) {
$r = q("SELECT id FROM `gcontact` WHERE `name`='%s'", dbesc($_GET["q"]));
$r = q("SELECT id FROM `contact` WHERE `uid` = 0 AND `name` = '%s'", dbesc($_GET["q"]));
if (!count($r))
$r = q("SELECT `id` FROM `gcontact` WHERE `nick`='%s'", dbesc($_GET["q"]));
$r = q("SELECT `id` FROM `contact` WHERE `uid` = 0 AND `nick` = '%s'", dbesc($_GET["q"]));
if (count($r)) {
$k = 0;
@ -1383,7 +1373,6 @@
$user_info = api_get_user($a);
// get last newtork messages
// params
$count = (x($_REQUEST,'count')?$_REQUEST['count']:20);
$page = (x($_REQUEST,'page')?$_REQUEST['page']-1:0);
@ -1410,7 +1399,7 @@
`contact`.`id` AS `cid`
FROM `item`
STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid`
AND NOT `contact`.`blocked` AND NOT `contact`.`pending`
AND (NOT `contact`.`blocked` OR `contact`.`pending`)
WHERE `item`.`uid` = %d AND `verb` = '%s'
AND `item`.`visible` AND NOT `item`.`moderated` AND NOT `item`.`deleted`
$sql_extra
@ -1487,7 +1476,7 @@
`user`.`nickname`, `user`.`hidewall`
FROM `item`
STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid`
AND NOT `contact`.`blocked` AND NOT `contact`.`pending`
AND (NOT `contact`.`blocked` OR `contact`.`pending`)
STRAIGHT_JOIN `user` ON `user`.`uid` = `item`.`uid`
AND NOT `user`.`hidewall`
WHERE `verb` = '%s' AND `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated`
@ -1554,7 +1543,7 @@
`contact`.`id` AS `cid`
FROM `item`
INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid`
AND NOT `contact`.`blocked` AND NOT `contact`.`pending`
AND (NOT `contact`.`blocked` OR `contact`.`pending`)
WHERE `item`.`visible` AND NOT `item`.`moderated` AND NOT `item`.`deleted`
AND `item`.`uid` = %d AND `item`.`verb` = '%s'
$sql_extra",
@ -1630,7 +1619,7 @@
`contact`.`id` AS `cid`
FROM `item`
STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid`
AND NOT `contact`.`blocked` AND NOT `contact`.`pending`
AND (NOT `contact`.`blocked` OR `contact`.`pending`)
WHERE `item`.`parent` = %d AND `item`.`visible`
AND NOT `item`.`moderated` AND NOT `item`.`deleted`
AND `item`.`uid` = %d AND `item`.`verb` = '%s'
@ -1684,7 +1673,7 @@
`contact`.`id` AS `cid`
FROM `item`
INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid`
AND NOT `contact`.`blocked` AND NOT `contact`.`pending`
AND (NOT `contact`.`blocked` OR `contact`.`pending`)
WHERE `item`.`visible` AND NOT `item`.`moderated` AND NOT `item`.`deleted`
AND NOT `item`.`private` AND `item`.`allow_cid` = '' AND `item`.`allow`.`gid` = ''
AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
@ -1803,7 +1792,7 @@
`contact`.`id` AS `cid`
FROM `item` FORCE INDEX (`uid_id`)
STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid`
AND NOT `contact`.`blocked` AND NOT `contact`.`pending`
AND (NOT `contact`.`blocked` OR `contact`.`pending`)
WHERE `item`.`uid` = %d AND `verb` = '%s'
AND NOT (`item`.`author-link` IN ('https://%s', 'http://%s'))
AND `item`.`visible` AND NOT `item`.`moderated` AND NOT `item`.`deleted`
@ -1877,7 +1866,7 @@
`contact`.`id` AS `cid`
FROM `item` FORCE INDEX (`uid_contactid_id`)
STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid`
AND NOT `contact`.`blocked` AND NOT `contact`.`pending`
AND (NOT `contact`.`blocked` OR `contact`.`pending`)
WHERE `item`.`uid` = %d AND `verb` = '%s'
AND `item`.`contact-id` = %d
AND `item`.`visible` AND NOT `item`.`moderated` AND NOT `item`.`deleted`
@ -2013,7 +2002,7 @@
AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0
AND `item`.`starred` = 1
AND `contact`.`id` = `item`.`contact-id`
AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
AND (NOT `contact`.`blocked` OR `contact`.`pending`)
$sql_extra
AND `item`.`id`>%d
ORDER BY `item`.`id` DESC LIMIT %d ,%d ",
@ -2412,6 +2401,59 @@
}
/**
* @brief return data from profiles
*
* @param array $profile array containing data from db table 'profile'
* @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
* @return array
*/
function api_format_items_profiles(&$profile = null, $type = "json") {
if ($profile != null) {
$profile = array('profile_id' => $profile['id'],
'profile_name' => $profile['profile-name'],
'is_default' => $profile['is-default'] ? true : false,
'hide_friends'=> $profile['hide-friends'] ? true : false,
'profile_photo' => $profile['photo'],
'profile_thumb' => $profile['thumb'],
'publish' => $profile['publish'] ? true : false,
'net_publish' => $profile['net-publish'] ? true : false,
'description' => $profile['pdesc'],
'date_of_birth' => $profile['dob'],
'address' => $profile['address'],
'city' => $profile['locality'],
'region' => $profile['region'],
'postal_code' => $profile['postal-code'],
'country' => $profile['country-name'],
'hometown' => $profile['hometown'],
'gender' => $profile['gender'],
'marital' => $profile['marital'],
'marital_with' => $profile['with'],
'marital_since' => $profile['howlong'],
'sexual' => $profile['sexual'],
'politic' => $profile['politic'],
'religion' => $profile['religion'],
'public_keywords' => $profile['pub_keywords'],
'private_keywords' => $profile['prv_keywords'],
'likes' => bbcode(api_clean_plain_items($profile['likes']), false, false, 2, false),
'dislikes' => bbcode(api_clean_plain_items($profile['dislikes']), false, false, 2, false),
'about' => bbcode(api_clean_plain_items($profile['about']), false, false, 2, false),
'music' => bbcode(api_clean_plain_items($profile['music']), false, false, 2, false),
'book' => bbcode(api_clean_plain_items($profile['book']), false, false, 2, false),
'tv' => bbcode(api_clean_plain_items($profile['tv']), false, false, 2, false),
'film' => bbcode(api_clean_plain_items($profile['film']), false, false, 2, false),
'interest' => bbcode(api_clean_plain_items($profile['interest']), false, false, 2, false),
'romance' => bbcode(api_clean_plain_items($profile['romance']), false, false, 2, false),
'work' => bbcode(api_clean_plain_items($profile['work']), false, false, 2, false),
'education' => bbcode(api_clean_plain_items($profile['education']), false, false, 2, false),
'social_networks' => bbcode(api_clean_plain_items($profile['contact']), false, false, 2, false),
'homepage' => $profile['homepage'],
'users' => null);
return $profile;
}
}
/**
* @brief format items to be returned by api
*
@ -2434,43 +2476,7 @@
if ($filter_user AND ($status_user["id"] != $user_info["id"]))
continue;
if ($item['thr-parent'] != $item['uri']) {
$r = q("SELECT id FROM item WHERE uid=%d AND uri='%s' LIMIT 1",
intval(api_user()),
dbesc($item['thr-parent']));
if ($r)
$in_reply_to_status_id = intval($r[0]['id']);
else
$in_reply_to_status_id = intval($item['parent']);
$in_reply_to_status_id_str = (string) intval($item['parent']);
$in_reply_to_screen_name = NULL;
$in_reply_to_user_id = NULL;
$in_reply_to_user_id_str = NULL;
$r = q("SELECT `author-link` FROM item WHERE uid=%d AND id=%d LIMIT 1",
intval(api_user()),
intval($in_reply_to_status_id));
if ($r) {
$r = q("SELECT * FROM `gcontact` WHERE `url` = '%s'", dbesc(normalise_link($r[0]['author-link'])));
if ($r) {
if ($r[0]['nick'] == "")
$r[0]['nick'] = api_get_nick($r[0]["url"]);
$in_reply_to_screen_name = (($r[0]['nick']) ? $r[0]['nick'] : $r[0]['name']);
$in_reply_to_user_id = intval($r[0]['id']);
$in_reply_to_user_id_str = (string) intval($r[0]['id']);
}
}
} else {
$in_reply_to_screen_name = NULL;
$in_reply_to_user_id = NULL;
$in_reply_to_status_id = NULL;
$in_reply_to_user_id_str = NULL;
$in_reply_to_status_id_str = NULL;
}
$in_reply_to = api_in_reply_to($item);
$converted = api_convert_item($item);
@ -2483,14 +2489,14 @@
'text' => $converted["text"],
'truncated' => False,
'created_at'=> api_date($item['created']),
'in_reply_to_status_id' => $in_reply_to_status_id,
'in_reply_to_status_id_str' => $in_reply_to_status_id_str,
'in_reply_to_status_id' => $in_reply_to['status_id'],
'in_reply_to_status_id_str' => $in_reply_to['status_id_str'],
'source' => (($item['app']) ? $item['app'] : 'web'),
'id' => intval($item['id']),
'id_str' => (string) intval($item['id']),
'in_reply_to_user_id' => $in_reply_to_user_id,
'in_reply_to_user_id_str' => $in_reply_to_user_id_str,
'in_reply_to_screen_name' => $in_reply_to_screen_name,
'in_reply_to_user_id' => $in_reply_to['user_id'],
'in_reply_to_user_id_str' => $in_reply_to['user_id_str'],
'in_reply_to_screen_name' => $in_reply_to['screen_name'],
$geo => NULL,
'favorited' => $item['starred'] ? true : false,
'user' => $status_user ,
@ -2642,7 +2648,7 @@
if ($user_info['self'] == 0)
$sql_extra = " AND false ";
$r = q("SELECT `nurl` FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 AND `pending` = 0 $sql_extra",
$r = q("SELECT `nurl` FROM `contact` WHERE `uid` = %d AND NOT `self` AND (NOT `blocked` OR `pending`) $sql_extra",
intval(api_user())
);
@ -2743,7 +2749,9 @@
$stringify_ids = (x($_REQUEST,'stringify_ids')?$_REQUEST['stringify_ids']:false);
$r = q("SELECT `gcontact`.`id` FROM `contact`, `gcontact` WHERE `contact`.`nurl` = `gcontact`.`nurl` AND `uid` = %d AND NOT `self` AND NOT `blocked` AND NOT `pending` $sql_extra",
$r = q("SELECT `pcontact`.`id` FROM `contact`
INNER JOIN `contact` AS `pcontact` ON `contact`.`nurl` = `pcontact`.`nurl` AND `pcontact`.`uid` = 0
WHERE `contact`.`uid` = %s AND NOT `contact`.`self`",
intval(api_user())
);
@ -2838,7 +2846,7 @@
* @brief delete a direct_message from mail table through api
*
* @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
* @return string
* @return string
*/
function api_direct_messages_destroy($type){
$a = get_app();
@ -3215,10 +3223,10 @@
}
$attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body);
// Skip if there is no shared message in there
// we already checked this in diaspora::is_reshare()
// but better one more than one less...
if ($body == $attributes)
// Skip if there is no shared message in there
// we already checked this in diaspora::is_reshare()
// but better one more than one less...
if ($body == $attributes)
return false;
@ -3297,7 +3305,7 @@
$nick = "";
$r = q("SELECT `nick` FROM `gcontact` WHERE `nurl` = '%s'",
$r = q("SELECT `nick` FROM `contact` WHERE `uid` = 0 AND `nurl` = '%s'",
dbesc(normalise_link($profile)));
if ($r)
$nick = $r[0]["nick"];
@ -3356,6 +3364,60 @@
return(false);
}
function api_in_reply_to($item) {
$in_reply_to = array();
$in_reply_to['status_id'] = NULL;
$in_reply_to['user_id'] = NULL;
$in_reply_to['status_id_str'] = NULL;
$in_reply_to['user_id_str'] = NULL;
$in_reply_to['screen_name'] = NULL;
if (($item['thr-parent'] != $item['uri']) AND (intval($item['parent']) != intval($item['id']))) {
$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' LIMIT 1",
intval($item['uid']),
dbesc($item['thr-parent']));
if (dbm::is_result($r)) {
$in_reply_to['status_id'] = intval($r[0]['id']);
} else {
$in_reply_to['status_id'] = intval($item['parent']);
}
$in_reply_to['status_id_str'] = (string) intval($in_reply_to['status_id']);
$r = q("SELECT `contact`.`nick`, `contact`.`name`, `contact`.`id`, `contact`.`url` FROM item
STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`author-id`
WHERE `item`.`id` = %d LIMIT 1",
intval($in_reply_to['status_id'])
);
if (dbm::is_result($r)) {
if ($r[0]['nick'] == "") {
$r[0]['nick'] = api_get_nick($r[0]["url"]);
}
$in_reply_to['screen_name'] = (($r[0]['nick']) ? $r[0]['nick'] : $r[0]['name']);
$in_reply_to['user_id'] = intval($r[0]['id']);
$in_reply_to['user_id_str'] = (string) intval($r[0]['id']);
}
// There seems to be situation, where both fields are identical:
// https://github.com/friendica/friendica/issues/1010
// This is a bugfix for that.
if (intval($in_reply_to['status_id']) == intval($item['id'])) {
logger('this message should never appear: id: '.$item['id'].' similar to reply-to: '.$in_reply_to['status_id'], LOGGER_DEBUG);
$in_reply_to['status_id'] = NULL;
$in_reply_to['user_id'] = NULL;
$in_reply_to['status_id_str'] = NULL;
$in_reply_to['user_id_str'] = NULL;
$in_reply_to['screen_name'] = NULL;
}
}
return $in_reply_to;
}
function api_clean_plain_items($Text) {
$include_entities = strtolower(x($_REQUEST,'include_entities')?$_REQUEST['include_entities']:"false");
@ -3880,6 +3942,71 @@
api_register_func('api/friendica/direct_messages_search', 'api_friendica_direct_messages_search', true);
/**
* @brief return data of all the profiles a user has to the client
*
* @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
* @return string
*/
function api_friendica_profile_show($type){
$a = get_app();
if (api_user()===false) throw new ForbiddenException();
// input params
$profileid = (x($_REQUEST,'profile_id') ? $_REQUEST['profile_id'] : 0);
// retrieve general information about profiles for user
$multi_profiles = feature_enabled(api_user(),'multi_profiles');
$directory = get_config('system', 'directory');
// get data of the specified profile id or all profiles of the user if not specified
if ($profileid != 0) {
$r = q("SELECT * FROM `profile` WHERE `uid` = %d AND `id` = %d",
intval(api_user()),
intval($profileid));
// error message if specified gid is not in database
if (count($r) == 0)
throw new BadRequestException("profile_id not available");
}
else
$r = q("SELECT * FROM `profile` WHERE `uid` = %d",
intval(api_user()));
// loop through all returned profiles and retrieve data and users
$k = 0;
foreach ($r as $rr) {
$profile = api_format_items_profiles($rr, $type);
// select all users from contact table, loop and prepare standard return for user data
$users = array();
$r = q("SELECT `id`, `nurl` FROM `contact` WHERE `uid`= %d AND `profile-id` = %d",
intval(api_user()),
intval($rr['profile_id']));
foreach ($r as $rr) {
$user = api_get_user($a, $rr['nurl']);
($type == "xml") ? $users[$k++.":user"] = $user : $users[] = $user;
}
$profile['users'] = $users;
// add prepared profile data to array for final return
if ($type == "xml") {
$profiles[$k++.":profile"] = $profile;
} else {
$profiles[] = $profile;
}
}
// return settings, authenticated user and profiles data
$result = array('multi_profiles' => $multi_profiles ? true : false,
'global_dir' => $directory,
'friendica_owner' => api_get_user($a, intval(api_user())),
'profiles' => $profiles);
return api_format_data("friendica_profiles", $type, array('$result' => $result));
}
api_register_func('api/friendica/profile/show', 'api_friendica_profile_show', true, API_METHOD_GET);
/*
To.Do:
[pagename] => api/1.1/statuses/lookup.json
@ -3905,6 +4032,9 @@ account/update_profile_background_image
account/update_profile_image
blocks/create
blocks/destroy
friendica/profile/update
friendica/profile/create
friendica/profile/delete
Not implemented in status.net:
statuses/retweeted_to_me

View file

@ -47,11 +47,10 @@ require_once("boot.php");
global $a, $db;
if(is_null($a)) {
if (is_null($a))
$a = new App;
}
if(is_null($db)) {
if (is_null($db)) {
@include(".htconfig.php");
require_once("include/dba.php");
$db = new dba($db_host, $db_user, $db_pass, $db_data);
@ -66,162 +65,261 @@ $bDebug = get_config('jabber','debug');
$oAuth = new exAuth($sLogFile, $bDebug);
class exAuth
{
class exAuth {
private $sLogFile;
private $bDebug;
private $rLogFile;
public function __construct($sLogFile, $bDebug)
{
/**
* @brief Create the class and do the authentification studd
*
* @param string $sLogFile The logfile name
* @param boolean $bDebug Debug mode
*/
public function __construct($sLogFile, $bDebug) {
global $db;
// setter
$this->sLogFile = $sLogFile;
$this->bDebug = $bDebug;
// ovo ne provjeravamo jer ako ne mozes kreirati log file, onda si u kvascu :)
// Open the logfile if the logfile name is defined
if ($this->sLogFile != '')
$this->rLogFile = fopen($this->sLogFile, "a") or die("Error opening log file: ". $this->sLogFile);
$this->writeLog("[exAuth] start");
// ovdje bi trebali biti spojeni na MySQL, imati otvoren log i zavrtit cekalicu
// We are connected to the SQL server and are having a log file.
do {
$iHeader = fgets(STDIN, 3);
$aLength = unpack("n", $iHeader);
$iLength = $aLength["1"];
if($iLength > 0) {
// ovo znaci da smo nesto dobili
$sData = fgets(STDIN, $iLength + 1);
$this->writeDebugLog("[debug] received data: ". $sData);
$aCommand = explode(":", $sData);
if (is_array($aCommand)){
switch ($aCommand[0]){
case "isuser":
// provjeravamo je li korisnik dobar
if (!isset($aCommand[1])){
$this->writeLog("[exAuth] invalid isuser command, no username given");
fwrite(STDOUT, pack("nn", 2, 0));
} else {
// ovdje provjeri je li korisnik OK
$sUser = str_replace(array("%20", "(a)"), array(" ", "@"), $aCommand[1]);
$this->writeDebugLog("[debug] checking isuser for ". $sUser);
$sQuery = "SELECT `uid` FROM `user` WHERE `nickname`='". $db->escape($sUser) ."'";
$this->writeDebugLog("[debug] using query ". $sQuery);
if ($oResult = q($sQuery)){
if ($oResult) {
// korisnik OK
$this->writeLog("[exAuth] valid user: ". $sUser);
fwrite(STDOUT, pack("nn", 2, 1));
} else {
// korisnik nije OK
$this->writeLog("[exAuth] invalid user: ". $sUser);
fwrite(STDOUT, pack("nn", 2, 0));
}
//$oResult->close();
} else {
$this->writeLog("[MySQL] invalid query: ". $sQuery);
fwrite(STDOUT, pack("nn", 2, 0));
}
}
break;
case "auth":
// provjeravamo autentifikaciju korisnika
if (sizeof($aCommand) != 4){
$this->writeLog("[exAuth] invalid auth command, data missing");
fwrite(STDOUT, pack("nn", 2, 0));
} else {
// ovdje provjeri prijavu
$sUser = str_replace(array("%20", "(a)"), array(" ", "@"), $aCommand[1]);
$this->writeDebugLog("[debug] doing auth for ". $sUser);
//$sQuery = "SELECT `uid`, `password` FROM `user` WHERE `password`='".hash('whirlpool',$aCommand[3])."' AND `nickname`='". $db->escape($sUser) ."'";
$sQuery = "SELECT `uid`, `password` FROM `user` WHERE `nickname`='". $db->escape($sUser) ."'";
$this->writeDebugLog("[debug] using query ". $sQuery);
if ($oResult = q($sQuery)){
$uid = $oResult[0]["uid"];
$Error = ($oResult[0]["password"] != hash('whirlpool',$aCommand[3]));
/*
if ($oResult[0]["password"] == hash('whirlpool',$aCommand[3])) {
// korisnik OK
$this->writeLog("[exAuth] authentificated user ". $sUser ."@". $aCommand[2]);
fwrite(STDOUT, pack("nn", 2, 1));
} else {
// korisnik nije OK
$this->writeLog("[exAuth] authentification failed for user ". $sUser ."@". $aCommand[2]);
fwrite(STDOUT, pack("nn", 2, 0));
}
$oResult->close();
*/
} else {
$this->writeLog("[MySQL] invalid query: ". $sQuery);
$Error = true;
$uid = -1;
}
if ($Error) {
$oConfig = q("SELECT `v` FROM `pconfig` WHERE `uid`=%d AND `cat` = 'xmpp' AND `k`='password' LIMIT 1;", intval($uid));
$this->writeLog("[exAuth] got password ".$oConfig[0]["v"]);
$Error = ($aCommand[3] != $oConfig[0]["v"]);
}
if ($Error) {
$this->writeLog("[exAuth] authentification failed for user ". $sUser ."@". $aCommand[2]);
fwrite(STDOUT, pack("nn", 2, 0));
} else {
$this->writeLog("[exAuth] authentificated user ". $sUser ."@". $aCommand[2]);
fwrite(STDOUT, pack("nn", 2, 1));
}
}
break;
case "setpass":
// postavljanje zaporke, onemoguceno
$this->writeLog("[exAuth] setpass command disabled");
fwrite(STDOUT, pack("nn", 2, 0));
break;
default:
// ako je uhvaceno ista drugo
$this->writeLog("[exAuth] unknown command ". $aCommand[0]);
fwrite(STDOUT, pack("nn", 2, 0));
break;
}
} else {
$this->writeDebugLog("[debug] invalid command string");
fwrite(STDOUT, pack("nn", 2, 0));
}
// Quit if the database connection went down
if (!$db->connected()) {
$this->writeDebugLog("[debug] the database connection went down");
return;
}
$iHeader = fgets(STDIN, 3);
$aLength = unpack("n", $iHeader);
$iLength = $aLength["1"];
// No data? Then quit
if ($iLength == 0) {
$this->writeDebugLog("[debug] we got no data");
return;
}
// Fetching the data
$sData = fgets(STDIN, $iLength + 1);
$this->writeDebugLog("[debug] received data: ". $sData);
$aCommand = explode(":", $sData);
if (is_array($aCommand)) {
switch ($aCommand[0]) {
case "isuser":
// Check the existance of a given username
$this->isuser($aCommand);
break;
case "auth":
// Check if the givven password is correct
$this->auth($aCommand);
break;
case "setpass":
// We don't accept the setting of passwords here
$this->writeLog("[exAuth] setpass command disabled");
fwrite(STDOUT, pack("nn", 2, 0));
break;
default:
// We don't know the given command
$this->writeLog("[exAuth] unknown command ". $aCommand[0]);
fwrite(STDOUT, pack("nn", 2, 0));
break;
}
} else {
$this->writeDebugLog("[debug] invalid command string");
fwrite(STDOUT, pack("nn", 2, 0));
}
unset ($iHeader);
unset ($aLength);
unset ($iLength);
unset($aCommand);
} while (true);
}
public function __destruct()
{
// zatvori log file
/**
* @brief Check if the given username exists
*
* @param array $aCommand The command array
*/
private function isuser($aCommand) {
global $a;
// Check if there is a username
if (!isset($aCommand[1])) {
$this->writeLog("[exAuth] invalid isuser command, no username given");
fwrite(STDOUT, pack("nn", 2, 0));
return;
}
// Now we check if the given user is valid
$sUser = str_replace(array("%20", "(a)"), array(" ", "@"), $aCommand[1]);
$this->writeDebugLog("[debug] checking isuser for ". $sUser."@".$aCommand[2]);
// If the hostnames doesn't match, we try to check remotely
if ($a->get_hostname() != $aCommand[2])
$found = $this->check_user($aCommand[2], $aCommand[1], true);
else {
$sQuery = "SELECT `uid` FROM `user` WHERE `nickname`='".dbesc($sUser)."'";
$this->writeDebugLog("[debug] using query ". $sQuery);
$r = q($sQuery);
$found = dbm::is_result($r);
}
if ($found) {
// The user is okay
$this->writeLog("[exAuth] valid user: ". $sUser);
fwrite(STDOUT, pack("nn", 2, 1));
} else {
// The user isn't okay
$this->writeLog("[exAuth] invalid user: ". $sUser);
fwrite(STDOUT, pack("nn", 2, 0));
}
}
/**
* @brief Check remote user existance via HTTP(S)
*
* @param string $host The hostname
* @param string $user Username
* @param boolean $ssl Should the check be done via SSL?
*
* @return boolean Was the user found?
*/
private function check_user($host, $user, $ssl) {
$url = ($ssl ? "https":"http")."://".$host."/noscrape/".$user;
$data = z_fetch_url($url);
if (!is_array($data))
return(false);
if ($data["return_code"] != "200")
return(false);
$json = @json_decode($data["body"]);
if (!is_object($json))
return(false);
return($json->nick == $user);
}
/**
* @brief Authenticate the givven user and password
*
* @param array $aCommand The command array
*/
private function auth($aCommand) {
global $a;
// check user authentication
if (sizeof($aCommand) != 4) {
$this->writeLog("[exAuth] invalid auth command, data missing");
fwrite(STDOUT, pack("nn", 2, 0));
return;
}
// We now check if the password match
$sUser = str_replace(array("%20", "(a)"), array(" ", "@"), $aCommand[1]);
$this->writeDebugLog("[debug] doing auth for ".$sUser."@".$aCommand[2]);
// If the hostnames doesn't match, we try to authenticate remotely
if ($a->get_hostname() != $aCommand[2])
$Error = !$this->check_credentials($aCommand[2], $aCommand[1], $aCommand[3], true);
else {
$sQuery = "SELECT `uid`, `password` FROM `user` WHERE `nickname`='".dbesc($sUser)."'";
$this->writeDebugLog("[debug] using query ". $sQuery);
if ($oResult = q($sQuery)) {
$uid = $oResult[0]["uid"];
$Error = ($oResult[0]["password"] != hash('whirlpool',$aCommand[3]));
} else {
$this->writeLog("[MySQL] invalid query: ". $sQuery);
$Error = true;
$uid = -1;
}
if ($Error) {
$oConfig = q("SELECT `v` FROM `pconfig` WHERE `uid` = %d AND `cat` = 'xmpp' AND `k`='password' LIMIT 1;", intval($uid));
$this->writeLog("[exAuth] got password ".$oConfig[0]["v"]);
$Error = ($aCommand[3] != $oConfig[0]["v"]);
}
}
if ($Error) {
$this->writeLog("[exAuth] authentification failed for user ".$sUser."@". $aCommand[2]);
fwrite(STDOUT, pack("nn", 2, 0));
} else {
$this->writeLog("[exAuth] authentificated user ".$sUser."@".$aCommand[2]);
fwrite(STDOUT, pack("nn", 2, 1));
}
}
/**
* @brief Check remote credentials via HTTP(S)
*
* @param string $host The hostname
* @param string $user Username
* @param string $password Password
* @param boolean $ssl Should the check be done via SSL?
*
* @return boolean Are the credentials okay?
*/
private function check_credentials($host, $user, $password, $ssl) {
$this->writeDebugLog("[debug] check credentials for user ".$user." on ".$host);
$url = ($ssl ? "https":"http")."://".$host."/api/account/verify_credentials.json";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, $user.':'.$password);
$header = curl_exec($ch);
$curl_info = @curl_getinfo($ch);
$http_code = $curl_info["http_code"];
curl_close($ch);
$this->writeDebugLog("[debug] got HTTP code ".$http_code);
return ($http_code == 200);
}
/**
* @brief write data to the logfile
*
* @param string $sMessage The logfile message
*/
private function writeLog($sMessage) {
if (is_resource($this->rLogFile))
fwrite($this->rLogFile, date("r")." ".$sMessage."\n");
}
/**
* @brief write debug data to the logfile
*
* @param string $sMessage The logfile message
*/
private function writeDebugLog($sMessage) {
if ($this->bDebug)
$this->writeLog($sMessage);
}
/**
* @brief destroy the class
*/
public function __destruct() {
// close the log file
$this->writeLog("[exAuth] stop");
if (is_resource($this->rLogFile)){
if (is_resource($this->rLogFile))
fclose($this->rLogFile);
}
}
private function writeLog($sMessage)
{
if (is_resource($this->rLogFile)) {
fwrite($this->rLogFile, date("r") ." ". $sMessage ."\n");
}
}
private function writeDebugLog($sMessage)
{
if ($this->bDebug){
$this->writeLog($sMessage);
}
}
}
?>

View file

@ -15,24 +15,27 @@ require_once("library/html-to-markdown/HTML_To_Markdown.php");
function diaspora2bb($s) {
$s = html_entity_decode($s,ENT_COMPAT,'UTF-8');
$s = html_entity_decode($s, ENT_COMPAT, 'UTF-8');
// Remove CR to avoid problems with following code
$s = str_replace("\r","",$s);
// Handles single newlines
$s = str_replace("\r", '<br>', $s);
$s = str_replace("\n"," \n",$s);
$s = str_replace("\n", " \n", $s);
// Replace lonely stars in lines not starting with it with literal stars
$s = preg_replace('/^([^\*]+)\*([^\*]*)$/im', '$1\*$2', $s);
// The parser cannot handle paragraphs correctly
$s = str_replace(array("</p>", "<p>", '<p dir="ltr">'),array("<br>", "<br>", "<br>"),$s);
$s = str_replace(array('</p>', '<p>', '<p dir="ltr">'), array('<br>', '<br>', '<br>'), $s);
// Escaping the hash tags
$s = preg_replace('/\#([^\s\#])/','&#35;$1',$s);
$s = preg_replace('/\#([^\s\#])/', '&#35;$1', $s);
$s = Markdown($s);
$s = preg_replace('/\@\{(.+?)\; (.+?)\@(.+?)\}/','@[url=https://$3/u/$2]$1[/url]',$s);
$s = preg_replace('/\@\{(.+?)\; (.+?)\@(.+?)\}/', '@[url=https://$3/u/$2]$1[/url]', $s);
$s = str_replace('&#35;','#',$s);
$s = str_replace('&#35;', '#', $s);
$search = array(" \n", "\n ");
$replace = array("\n", "\n");
@ -41,23 +44,24 @@ function diaspora2bb($s) {
$s = str_replace($search, $replace, $s);
} while ($oldtext != $s);
$s = str_replace("\n\n", "<br>", $s);
$s = str_replace("\n\n", '<br>', $s);
$s = html2bbcode($s);
// protect the recycle symbol from turning into a tag, but without unescaping angles and naked ampersands
$s = str_replace('&#x2672;',html_entity_decode('&#x2672;',ENT_QUOTES,'UTF-8'),$s);
$s = str_replace('&#x2672;', html_entity_decode('&#x2672;', ENT_QUOTES, 'UTF-8'), $s);
// Convert everything that looks like a link to a link
$s = preg_replace("/([^\]\=]|^)(https?\:\/\/)([a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", '$1[url=$2$3]$2$3[/url]',$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 = 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);
$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);
$s = preg_replace('/(\[code\])+(.*?)(\[\/code\])+/ism', '[code]$2[/code]', $s);
// Don't show link to full picture (until it is fixed)
$s = scale_external_images($s, false);

View file

@ -1,4 +1,6 @@
<?php
use \Friendica\Core\Config;
require_once("include/oembed.php");
require_once('include/event.php');
require_once('include/map.php');
@ -146,7 +148,7 @@ function cleancss($input) {
if (($char >= "a") and ($char <= "z"))
$cleaned .= $char;
if (!(strpos(" #;:0123456789-_", $char) === false))
if (!(strpos(" #;:0123456789-_.%", $char) === false))
$cleaned .= $char;
}
@ -892,8 +894,7 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true, $simplehtml = fal
// we may need to restrict this further if it picks up too many strays
// link acct:user@host to a webfinger profile redirector
$Text = preg_replace('/acct:(.*?)@(.*?)([ ,])/', '<a href="' . $a->get_baseurl() . '/acctlink?addr=' . "$1@$2"
. '" target="extlink" >acct:' . "$1@$2$3" . '</a>',$Text);
$Text = preg_replace('/acct:([^@]+)@((?!\-)(?:[a-zA-Z\d\-]{0,62}[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63})/', '<a href="' . $a->get_baseurl() . '/acctlink?addr=$1@$2" target="extlink">acct:$1@$2</a>',$Text);
// Perform MAIL Search
$Text = preg_replace("/\[mail\]([$MAILSearchString]*)\[\/mail\]/", '<a href="mailto:$1">$1</a>', $Text);
@ -1162,11 +1163,24 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true, $simplehtml = fal
$Text = preg_replace('/\&quot\;/','"',$Text);
// fix any escaped ampersands that may have been converted into links
$Text = preg_replace("/\<([^>]*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism",'<$1$2=$3&$4>',$Text);
$Text = preg_replace("/\<([^>]*?)(src|href)=\"(?!http|ftp|mailto|gopher|cid)(.*?)\>/ism",'<$1$2="">',$Text);
$Text = preg_replace('/\<([^>]*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism', '<$1$2=$3&$4>', $Text);
if($saved_image)
// sanitizes src attributes (only relative redir URIs or http URLs)
$Text = preg_replace('#<([^>]*?)(src)="(?!http|redir)(.*?)"(.*?)>#ism', '<$1$2=""$4 class="invalid-src" title="' . t('Invalid source protocol') . '">', $Text);
// sanitize href attributes (only whitelisted protocols URLs)
// default value for backward compatibility
$allowed_link_protocols = Config::get('system', 'allowed_link_protocols', array('ftp', 'mailto', 'gopher', 'cid'));
// Always allowed protocol even if config isn't set or not including it
$allowed_link_protocols[] = 'http';
$regex = '#<([^>]*?)(href)="(?!' . implode('|', $allowed_link_protocols) . ')(.*?)"(.*?)>#ism';
$Text = preg_replace($regex, '<$1$2="javascript:void(0)"$4 class="invalid-href" title="' . t('Invalid link protocol') . '">', $Text);
if($saved_image) {
$Text = bb_replace_images($Text, $saved_image);
}
// Clean up the HTML by loading and saving the HTML with the DOM.
// Bad structured html can break a whole page.

View file

@ -1,72 +1,210 @@
<?php
/**
* @file include/cache.php
*
* @brief Class for storing data for a short time
*/
use \Friendica\Core\Config;
use \Friendica\Core\PConfig;
class Cache {
/**
* cache api
* @brief Check for memcache and open a connection if configured
*
* @return object|boolean The memcache object - or "false" if not successful
*/
public static function memcache() {
if (!function_exists('memcache_connect')) {
return false;
}
class Cache {
public static function get($key) {
if (!Config::get('system', 'memcache')) {
return false;
}
$r = q("SELECT `v` FROM `cache` WHERE `k`='%s' limit 1",
dbesc($key)
);
$memcache_host = Config::get('system', 'memcache_host', '127.0.0.1');
$memcache_port = Config::get('system', 'memcache_port', 11211);
if (count($r))
return $r[0]['v'];
$memcache = new Memcache;
if (!$memcache->connect($memcache_host, $memcache_port)) {
return false;
}
return $memcache;
}
/**
* @brief Return the duration for a given cache level
*
* @param integer $level Cache level
*
* @return integer The cache duration in seconds
*/
private function duration($level) {
switch($level) {
case CACHE_MONTH;
$seconds = 2592000;
break;
case CACHE_WEEK;
$seconds = 604800;
break;
case CACHE_DAY;
$seconds = 86400;
break;
case CACHE_HOUR;
$seconds = 3600;
break;
case CACHE_HALF_HOUR;
$seconds = 1800;
break;
case CACHE_QUARTER_HOUR;
$seconds = 900;
break;
case CACHE_FIVE_MINUTES;
$seconds = 300;
break;
case CACHE_MINUTE;
$seconds = 60;
break;
}
return $seconds;
}
/**
* @brief Fetch cached data according to the key
*
* @param string $key The key to the cached data
*
* @return mixed Cached $value or "null" if not found
*/
public static function get($key) {
$memcache = self::memcache();
if (is_object($memcache)) {
// We fetch with the hostname as key to avoid problems with other applications
$cached = $memcache->get(get_app()->get_hostname().":".$key);
$value = @unserialize($cached);
// Only return a value if the serialized value is valid.
// We also check if the db entry is a serialized
// boolean 'false' value (which we want to return).
if ($cached === serialize(false) || $value !== false) {
return $value;
}
return null;
}
public static function set($key,$value, $duration = CACHE_MONTH) {
// Frequently clear cache
self::clear($duration);
q("REPLACE INTO `cache` (`k`,`v`,`expire_mode`,`updated`) VALUES ('%s','%s',%d,'%s')",
dbesc($key),
dbesc($value),
intval($duration),
dbesc(datetime_convert()));
$r = q("SELECT `v` FROM `cache` WHERE `k`='%s' LIMIT 1",
dbesc($key)
);
if (dbm::is_result($r)) {
$cached = $r[0]['v'];
$value = @unserialize($cached);
// Only return a value if the serialized value is valid.
// We also check if the db entry is a serialized
// boolean 'false' value (which we want to return).
if ($cached === serialize(false) || $value !== false) {
return $value;
}
}
/*
*
* Leaving this legacy code temporaily to see how REPLACE fares
* as opposed to non-atomic checks when faced with fast moving key duplication.
* As a MySQL extension it isn't portable, but we're not yet very portable.
*/
/*
* $r = q("SELECT * FROM `cache` WHERE `k`='%s' limit 1",
* dbesc($key)
* );
* if(count($r)) {
* q("UPDATE `cache` SET `v` = '%s', `updated = '%s' WHERE `k` = '%s'",
* dbesc($value),
* dbesc(datetime_convert()),
* dbesc($key));
* }
* else {
* q("INSERT INTO `cache` (`k`,`v`,`updated`) VALUES ('%s','%s','%s')",
* dbesc($key),
* dbesc($value),
* dbesc(datetime_convert()));
* }
* }
*/
public static function clear(){
q("DELETE FROM `cache` WHERE `updated` < '%s' AND `expire_mode` = %d",
dbesc(datetime_convert('UTC','UTC',"now - 30 days")), intval(CACHE_MONTH));
q("DELETE FROM `cache` WHERE `updated` < '%s' AND `expire_mode` = %d",
dbesc(datetime_convert('UTC','UTC',"now - 7 days")), intval(CACHE_WEEK));
q("DELETE FROM `cache` WHERE `updated` < '%s' AND `expire_mode` = %d",
dbesc(datetime_convert('UTC','UTC',"now - 1 days")), intval(CACHE_DAY));
q("DELETE FROM `cache` WHERE `updated` < '%s' AND `expire_mode` = %d",
dbesc(datetime_convert('UTC','UTC',"now - 1 hours")), intval(CACHE_HOUR));
}
return null;
}
/**
* @brief Put data in the cache according to the key
*
* The input $value can have multiple formats.
*
* @param string $key The key to the cached data
* @param mixed $valie The value that is about to be stored
* @param integer $duration The cache lifespan
*/
public static function set($key, $value, $duration = CACHE_MONTH) {
// Do we have an installed memcache? Use it instead.
$memcache = self::memcache();
if (is_object($memcache)) {
// We store with the hostname as key to avoid problems with other applications
$memcache->set(get_app()->get_hostname().":".$key, serialize($value), MEMCACHE_COMPRESSED, self::duration($duration));
return;
}
/// @todo store the cache data in the same way like the config data
q("REPLACE INTO `cache` (`k`,`v`,`expire_mode`,`updated`) VALUES ('%s','%s',%d,'%s')",
dbesc($key),
dbesc(serialize($value)),
intval($duration),
dbesc(datetime_convert()));
}
/**
* @brief Remove outdated data from the cache
*
* @param integer $maxlevel The maximum cache level that is to be cleared
*/
public static function clear($max_level = CACHE_MONTH) {
// Clear long lasting cache entries only once a day
if (get_config("system", "cache_cleared_day") < time() - self::duration(CACHE_DAY)) {
if ($max_level == CACHE_MONTH) {
q("DELETE FROM `cache` WHERE `updated` < '%s' AND `expire_mode` = %d",
dbesc(datetime_convert('UTC','UTC',"now - 30 days")), intval(CACHE_MONTH));
}
if ($max_level <= CACHE_WEEK) {
q("DELETE FROM `cache` WHERE `updated` < '%s' AND `expire_mode` = %d",
dbesc(datetime_convert('UTC','UTC',"now - 7 days")), intval(CACHE_WEEK));
}
if ($max_level <= CACHE_DAY) {
q("DELETE FROM `cache` WHERE `updated` < '%s' AND `expire_mode` = %d",
dbesc(datetime_convert('UTC','UTC',"now - 1 days")), intval(CACHE_DAY));
}
set_config("system", "cache_cleared_day", time());
}
if (($max_level <= CACHE_HOUR) AND (get_config("system", "cache_cleared_hour")) < time() - self::duration(CACHE_HOUR)) {
q("DELETE FROM `cache` WHERE `updated` < '%s' AND `expire_mode` = %d",
dbesc(datetime_convert('UTC','UTC',"now - 1 hours")), intval(CACHE_HOUR));
set_config("system", "cache_cleared_hour", time());
}
if (($max_level <= CACHE_HALF_HOUR) AND (get_config("system", "cache_cleared_half_hour")) < time() - self::duration(CACHE_HALF_HOUR)) {
q("DELETE FROM `cache` WHERE `updated` < '%s' AND `expire_mode` = %d",
dbesc(datetime_convert('UTC','UTC',"now - 30 minutes")), intval(CACHE_HALF_HOUR));
set_config("system", "cache_cleared_half_hour", time());
}
if (($max_level <= CACHE_QUARTER_HOUR) AND (get_config("system", "cache_cleared_hour")) < time() - self::duration(CACHE_QUARTER_HOUR)) {
q("DELETE FROM `cache` WHERE `updated` < '%s' AND `expire_mode` = %d",
dbesc(datetime_convert('UTC','UTC',"now - 15 minutes")), intval(CACHE_QUARTER_HOUR));
set_config("system", "cache_cleared_quarter_hour", time());
}
if (($max_level <= CACHE_FIVE_MINUTES) AND (get_config("system", "cache_cleared_five_minute")) < time() - self::duration(CACHE_FIVE_MINUTES)) {
q("DELETE FROM `cache` WHERE `updated` < '%s' AND `expire_mode` = %d",
dbesc(datetime_convert('UTC','UTC',"now - 5 minutes")), intval(CACHE_FIVE_MINUTES));
set_config("system", "cache_cleared_five_minute", time());
}
if (($max_level <= CACHE_MINUTE) AND (get_config("system", "cache_cleared_minute")) < time() - self::duration(CACHE_MINUTE)) {
q("DELETE FROM `cache` WHERE `updated` < '%s' AND `expire_mode` = %d",
dbesc(datetime_convert('UTC','UTC',"now - 1 minutes")), intval(CACHE_MINUTE));
set_config("system", "cache_cleared_minute", time());
}
}
}

View file

@ -439,7 +439,7 @@ These Fields are not added below (yet). They are here to for bug search.
function item_joins() {
return "STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND
NOT `contact`.`blocked` AND NOT `contact`.`pending`
(NOT `contact`.`blocked` OR `contact`.`pending`)
LEFT JOIN `contact` AS `author` ON `author`.`id`=`item`.`author-id`
LEFT JOIN `contact` AS `owner` ON `owner`.`id`=`item`.`owner-id`";
}
@ -903,79 +903,86 @@ function best_link_url($item,&$sparkle,$ssl_state = false) {
}
if(! function_exists('item_photo_menu')){
function item_photo_menu($item){
if (! function_exists('item_photo_menu')) {
function item_photo_menu($item)
{
$ssl_state = false;
if(local_user())
if(local_user()) {
$ssl_state = true;
}
$sub_link="";
$poke_link="";
$contact_url="";
$pm_url="";
$status_link="";
$photos_link="";
$posts_link="";
$network = "";
$sub_link = '';
$poke_link = '';
$contact_url = '';
$pm_url = '';
$status_link = '';
$photos_link = '';
$posts_link = '';
$network = '';
if((local_user()) && local_user() == $item['uid'] && $item['parent'] == $item['id'] && (! $item['self'])) {
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);
if($profile_link === 'mailbox')
$profile_link = best_link_url($item, $sparkle, $ssl_state);
if ($profile_link === 'mailbox') {
$profile_link = '';
}
$cid = 0;
$network = "";
$network = '';
$rel = 0;
$r = q("SELECT `id`, `network`, `rel` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' LIMIT 1",
intval(local_user()), dbesc(normalise_link($item['author-link'])));
if ($r) {
$cid = $r[0]["id"];
$network = $r[0]["network"];
$rel = $r[0]["rel"];
$cid = $r[0]['id'];
$network = $r[0]['network'];
$rel = $r[0]['rel'];
}
if($sparkle) {
$status_link = $profile_link."?url=status";
$photos_link = $profile_link."?url=photos";
$profile_link = $profile_link."?url=profile";
$status_link = $profile_link . '?url=status';
$photos_link = $profile_link . '?url=photos';
$profile_link = $profile_link . '?url=profile';
$zurl = '';
} else
} else {
$profile_link = zrl($profile_link);
}
if($cid && !$item['self']) {
$poke_link = 'poke/?f=&c='.$cid;
$contact_url = 'contacts/'.$cid;
$posts_link = 'contacts/'.$cid.'/posts';
if ($cid && !$item['self']) {
$poke_link = 'poke/?f=&c=' . $cid;
$contact_url = 'contacts/' . $cid;
$posts_link = 'contacts/' . $cid . '/posts';
if (in_array($network, array(NETWORK_DFRN, NETWORK_DIASPORA)))
$pm_url = 'message/new/'.$cid;
if (in_array($network, array(NETWORK_DFRN, NETWORK_DIASPORA))) {
$pm_url = 'message/new/' . $cid;
}
}
if (local_user()) {
$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("Edit Contact") => $contact_url,
t("Send PM") => $pm_url
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('View Contact') => $contact_url,
t('Send PM') => $pm_url
);
if ($network == NETWORK_DFRN)
if ($network == NETWORK_DFRN) {
$menu[t("Poke")] = $poke_link;
}
if ((($cid == 0) OR ($rel == CONTACT_IS_FOLLOWER)) AND
in_array($item['network'], array(NETWORK_DFRN, NETWORK_OSTATUS, NETWORK_DIASPORA)))
$menu[t("Connect/Follow")] = "follow?url=".urlencode($item['author-link']);
} else
$menu = array(t("View Profile") => $item['author-link']);
in_array($item['network'], array(NETWORK_DFRN, NETWORK_OSTATUS, NETWORK_DIASPORA))) {
$menu[t('Connect/Follow')] = 'follow?url=' . urlencode($item['author-link']);
}
} else {
$menu = array(t('View Profile') => $item['author-link']);
}
$args = array('item' => $item, 'menu' => $menu);
@ -983,13 +990,14 @@ function item_photo_menu($item){
$menu = $args['menu'];
$o = "";
foreach($menu as $k=>$v){
if(strpos($v,'javascript:') === 0) {
$v = substr($v,11);
$o .= "<li role=\"menuitem\"><a onclick=\"$v\">$k</a></li>\n";
$o = '';
foreach ($menu as $k => $v) {
if (strpos($v, 'javascript:') === 0) {
$v = substr($v, 11);
$o .= '<li role="menuitem"><a onclick="' . $v . '">' . $k . '</a></li>' . PHP_EOL;
} elseif ($v!='') {
$o .= '<li role="menuitem"><a href="' . $v . '">' . $k . '</a></li>' . PHP_EOL;
}
elseif ($v!="") $o .= "<li role=\"menuitem\"><a href=\"$v\">$k</a></li>\n";
}
return $o;
}}
@ -1139,7 +1147,7 @@ function format_like($cnt,$arr,$type,$id) {
$explikers = sprintf( t('%s don\'t attend.'), $likers);
break;
case 'attendmaybe':
$phrase = sprintf( t('<span %1$s>%2$d people</span> anttend maybe'), $spanatts, $cnt);
$phrase = sprintf( t('<span %1$s>%2$d people</span> attend maybe'), $spanatts, $cnt);
$explikers = sprintf( t('%s anttend maybe.'), $likers);
break;
}

View file

@ -0,0 +1,40 @@
<?php
/**
* @file include/create_shadowentry.php
* @brief This script creates posts with UID = 0 for a given public post.
*
* This script is started from mod/item.php to save some time when doing a post.
*/
require_once("boot.php");
require_once("include/threads.php");
function create_shadowentry_run($argv, $argc) {
global $a, $db;
if (is_null($a))
$a = new App;
if (is_null($db)) {
@include(".htconfig.php");
require_once("include/dba.php");
$db = new dba($db_host, $db_user, $db_pass, $db_data);
unset($db_host, $db_user, $db_pass, $db_data);
}
load_config('config');
load_config('system');
if ($argc != 2) {
return;
}
$message_id = intval($argv[1]);
add_shadow_entry($message_id);
}
if (array_search(__file__,get_included_files())===0){
create_shadowentry_run($_SERVER["argv"],$_SERVER["argc"]);
killme();
}
?>

View file

@ -11,6 +11,7 @@ if (!file_exists("boot.php") AND (sizeof($_SERVER["argv"]) != 0)) {
}
require_once("boot.php");
require_once("include/photos.php");
function cron_run(&$argv, &$argc){
@ -69,15 +70,15 @@ function cron_run(&$argv, &$argc){
// run queue delivery process in the background
proc_run(PRIORITY_NEGLIGIBLE,"include/queue.php");
proc_run(PRIORITY_NEGLIGIBLE, "include/queue.php");
// run the process to discover global contacts in the background
proc_run(PRIORITY_LOW,"include/discover_poco.php");
proc_run(PRIORITY_LOW, "include/discover_poco.php");
// run the process to update locally stored global contacts in the background
proc_run(PRIORITY_LOW,"include/discover_poco.php", "checkcontact");
proc_run(PRIORITY_LOW, "include/discover_poco.php", "checkcontact");
// Expire and remove user entries
cron_expire_and_remove_users();
@ -120,11 +121,15 @@ function cron_run(&$argv, &$argc){
update_contact_birthdays();
proc_run(PRIORITY_LOW,"include/discover_poco.php", "suggestions");
proc_run(PRIORITY_LOW, "include/discover_poco.php", "suggestions");
set_config('system','last_expire_day',$d2);
proc_run(PRIORITY_LOW,'include/expire.php');
proc_run(PRIORITY_LOW, 'include/expire.php');
proc_run(PRIORITY_LOW, 'include/dbclean.php');
cron_update_photo_albums();
}
// Clear cache entries
@ -146,6 +151,20 @@ function cron_run(&$argv, &$argc){
return;
}
/**
* @brief Update the cached values for the number of photo albums per user
*/
function cron_update_photo_albums() {
$r = q("SELECT `uid` FROM `user` WHERE NOT `account_expired` AND NOT `account_removed`");
if (!dbm::is_result($r)) {
return;
}
foreach ($r AS $user) {
photo_albums($user['uid'], true);
}
}
/**
* @brief Expire and remove user entries
*/
@ -306,7 +325,11 @@ function cron_poll_contacts($argc, $argv) {
logger("Polling ".$contact["network"]." ".$contact["id"]." ".$contact["nick"]." ".$contact["name"]);
proc_run(PRIORITY_MEDIUM,'include/onepoll.php',$contact['id']);
if (($contact['network'] == NETWORK_FEED) AND ($contact['priority'] <= 3)) {
proc_run(PRIORITY_MEDIUM, 'include/onepoll.php', $contact['id']);
} else {
proc_run(PRIORITY_LOW, 'include/onepoll.php', $contact['id']);
}
if($interval)
@time_sleep_until(microtime(true) + (float) $interval);
@ -355,10 +378,10 @@ function cron_clear_cache(&$a) {
}
// Delete the cached OEmbed entries that are older than one year
q("DELETE FROM `oembed` WHERE `created` < NOW() - INTERVAL 1 YEAR");
q("DELETE FROM `oembed` WHERE `created` < NOW() - INTERVAL 3 MONTH");
// Delete the cached "parse_url" entries that are older than one year
q("DELETE FROM `parsed_url` WHERE `created` < NOW() - INTERVAL 1 YEAR");
q("DELETE FROM `parsed_url` WHERE `created` < NOW() - INTERVAL 3 MONTH");
// Maximum table size in megabyte
$max_tablesize = intval(get_config('system','optimize_max_tablesize')) * 1000000;

View file

@ -325,15 +325,15 @@ function datetimesel($format, $min, $max, $default, $label, $id = 'datetimepicke
* Results relative to current timezone.
* Limited to range of timestamps.
*
* @param string $posted_date
* @param string $posted_date MySQL-formatted date string (YYYY-MM-DD HH:MM:SS)
* @param string $format (optional) Parsed with sprintf()
* <tt>%1$d %2$s ago</tt>, e.g. 22 hours ago, 1 minute ago
*
* @return string with relative date
*/
function relative_date($posted_date,$format = null) {
function relative_date($posted_date, $format = null) {
$localtime = datetime_convert('UTC',date_default_timezone_get(),$posted_date);
$localtime = $posted_date . ' UTC';
$abs = strtotime($localtime);
@ -347,13 +347,6 @@ function relative_date($posted_date,$format = null) {
return t('less than a second ago');
}
/*
$time_append = '';
if ($etime >= 86400) {
$time_append = ' ('.$localtime.')';
}
*/
$a = array( 12 * 30 * 24 * 60 * 60 => array( t('year'), t('years')),
30 * 24 * 60 * 60 => array( t('month'), t('months')),
7 * 24 * 60 * 60 => array( t('week'), t('weeks')),
@ -368,10 +361,11 @@ function relative_date($posted_date,$format = null) {
if ($d >= 1) {
$r = round($d);
// translators - e.g. 22 hours ago, 1 minute ago
if(! $format)
if (!$format) {
$format = t('%1$d %2$s ago');
}
return sprintf( $format,$r, (($r == 1) ? $str[0] : $str[1]));
return sprintf($format, $r, (($r == 1) ? $str[0] : $str[1]));
}
}
}

View file

@ -5,7 +5,7 @@ require_once("dbm.php");
# TODO: PDO is disabled for release 3.3. We need to investigate why
# the update from 3.2 fails with pdo
/*
if(class_exists('\PDO') && in_array('mysql', PDO::getAvailableDrivers())) {
if (class_exists('\PDO') && in_array('mysql', PDO::getAvailableDrivers())) {
require_once("library/dddbl2/dddbl.php");
require_once("include/dba_pdo.php");
}
@ -24,7 +24,7 @@ require_once('include/datetime.php');
*
*/
if(! class_exists('dba')) {
if (! class_exists('dba')) {
class dba {
private $debug = 0;
@ -34,7 +34,7 @@ class dba {
public $connected = false;
public $error = false;
function __construct($server,$user,$pass,$db,$install = false) {
function __construct($server, $user, $pass, $db, $install = false) {
global $a;
$stamp1 = microtime(true);
@ -44,15 +44,15 @@ class dba {
$pass = trim($pass);
$db = trim($db);
if (!(strlen($server) && strlen($user))){
if (!(strlen($server) && strlen($user))) {
$this->connected = false;
$this->db = null;
return;
}
if($install) {
if(strlen($server) && ($server !== 'localhost') && ($server !== '127.0.0.1')) {
if(! dns_get_record($server, DNS_A + DNS_CNAME + DNS_PTR)) {
if ($install) {
if (strlen($server) && ($server !== 'localhost') && ($server !== '127.0.0.1')) {
if (! dns_get_record($server, DNS_A + DNS_CNAME + DNS_PTR)) {
$this->error = sprintf( t('Cannot locate DNS info for database server \'%s\''), $server);
$this->connected = false;
$this->db = null;
@ -61,27 +61,28 @@ class dba {
}
}
if(class_exists('mysqli')) {
if (class_exists('mysqli')) {
$this->db = @new mysqli($server,$user,$pass,$db);
if(! mysqli_connect_errno()) {
if (! mysqli_connect_errno()) {
$this->connected = true;
}
if (isset($a->config["system"]["db_charset"]))
if (isset($a->config["system"]["db_charset"])) {
$this->db->set_charset($a->config["system"]["db_charset"]);
}
else {
}
} else {
$this->mysqli = false;
$this->db = mysql_connect($server,$user,$pass);
if($this->db && mysql_select_db($db,$this->db)) {
if ($this->db && mysql_select_db($db,$this->db)) {
$this->connected = true;
}
if (isset($a->config["system"]["db_charset"]))
mysql_set_charset($a->config["system"]["db_charset"], $this->db);
}
if(! $this->connected) {
if (!$this->connected) {
$this->db = null;
if(! $install)
if (!$install) {
system_unavailable();
}
}
$a->save_timestamp($stamp1, "network");
@ -91,38 +92,91 @@ class dba {
return $this->db;
}
/**
* @brief Returns the MySQL server version string
*
* This function discriminate between the deprecated mysql API and the current
* object-oriented mysqli API. Example of returned string: 5.5.46-0+deb8u1
*
* @return string
*/
public function server_info() {
if ($this->mysqli) {
$return = $this->db->server_info;
} else {
$return = mysql_get_server_info($this->db);
}
return $return;
}
/**
* @brief Returns the selected database name
*
* @return string
*/
public function database_name() {
$r = $this->q("SELECT DATABASE() AS `db`");
return $r[0]['db'];
}
/**
* @brief Returns the number of rows
*
* @return integer
*/
public function num_rows() {
if (!$this->result) {
return 0;
}
if ($this->mysqli) {
$return = $this->result->num_rows;
} else {
$return = mysql_num_rows($this->result);
}
return $return;
}
public function q($sql, $onlyquery = false) {
global $a;
if((! $this->db) || (! $this->connected))
if (!$this->db || !$this->connected) {
return false;
}
$this->error = '';
// Check the connection (This can reconnect the connection - if configured)
if ($this->mysqli)
if ($this->mysqli) {
$connected = $this->db->ping();
else
} else {
$connected = mysql_ping($this->db);
$connstr = ($connected ? "Connected": "Disonnected");
}
$connstr = ($connected ? "Connected" : "Disonnected");
$stamp1 = microtime(true);
if($this->mysqli)
$result = @$this->db->query($sql);
else
$result = @mysql_query($sql,$this->db);
$orig_sql = $sql;
if (x($a->config,'system') && x($a->config['system'], 'db_callstack')) {
$sql = "/*".$a->callstack()." */ ".$sql;
}
if ($this->mysqli) {
$result = @$this->db->query($sql);
} else {
$result = @mysql_query($sql,$this->db);
}
$stamp2 = microtime(true);
$duration = (float)($stamp2-$stamp1);
$a->save_timestamp($stamp1, "database");
if (strtolower(substr($sql, 0, 6)) != "select")
if (strtolower(substr($orig_sql, 0, 6)) != "select") {
$a->save_timestamp($stamp1, "database_write");
if(x($a->config,'system') && x($a->config['system'],'db_log')) {
}
if (x($a->config,'system') && x($a->config['system'],'db_log')) {
if (($duration > $a->config["system"]["db_loglimit"])) {
$duration = round($duration, 3);
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
@ -133,33 +187,34 @@ class dba {
}
}
if($this->mysqli) {
if($this->db->errno) {
if ($this->mysqli) {
if ($this->db->errno) {
$this->error = $this->db->error;
$this->errorno = $this->db->errno;
}
} elseif(mysql_errno($this->db)) {
} elseif (mysql_errno($this->db)) {
$this->error = mysql_error($this->db);
$this->errorno = mysql_errno($this->db);
}
if(strlen($this->error)) {
if (strlen($this->error)) {
logger('DB Error ('.$connstr.') '.$this->errorno.': '.$this->error);
}
if($this->debug) {
if ($this->debug) {
$mesg = '';
if($result === false)
if ($result === false) {
$mesg = 'false';
elseif($result === true)
} elseif ($result === true) {
$mesg = 'true';
else {
if($this->mysqli)
} else {
if ($this->mysqli) {
$mesg = $result->num_rows . ' results' . EOL;
else
} else {
$mesg = mysql_num_rows($result) . ' results' . EOL;
}
}
$str = 'SQL = ' . printable($sql) . EOL . 'SQL returned ' . $mesg
@ -175,30 +230,30 @@ class dba {
* These usually indicate SQL syntax errors that need to be resolved.
*/
if($result === false) {
if ($result === false) {
logger('dba: ' . printable($sql) . ' returned false.' . "\n" . $this->error);
if(file_exists('dbfail.out'))
if (file_exists('dbfail.out')) {
file_put_contents('dbfail.out', datetime_convert() . "\n" . printable($sql) . ' returned false' . "\n" . $this->error . "\n", FILE_APPEND);
}
}
if(($result === true) || ($result === false))
if (($result === true) || ($result === false)) {
return $result;
}
if ($onlyquery) {
$this->result = $result;
return true;
}
$r = array();
if($this->mysqli) {
if($result->num_rows) {
if ($this->mysqli) {
if ($result->num_rows) {
while($x = $result->fetch_array(MYSQLI_ASSOC))
$r[] = $x;
$result->free_result();
}
}
else {
if(mysql_num_rows($result)) {
} else {
if (mysql_num_rows($result)) {
while($x = mysql_fetch_array($result, MYSQL_ASSOC))
$r[] = $x;
mysql_free_result($result);
@ -207,33 +262,35 @@ class dba {
//$a->save_timestamp($stamp1, "database");
if($this->debug)
if ($this->debug) {
logger('dba: ' . printable(print_r($r, true)));
}
return($r);
}
public function qfetch() {
$x = false;
if ($this->result)
if($this->mysqli) {
if($this->result->num_rows)
if ($this->result) {
if ($this->mysqli) {
if ($this->result->num_rows)
$x = $this->result->fetch_array(MYSQLI_ASSOC);
} else {
if(mysql_num_rows($this->result))
if (mysql_num_rows($this->result))
$x = mysql_fetch_array($this->result, MYSQL_ASSOC);
}
}
return($x);
}
public function qclose() {
if ($this->result)
if($this->mysqli) {
if ($this->result) {
if ($this->mysqli) {
$this->result->free_result();
} else {
mysql_free_result($this->result);
}
}
}
public function dbg($dbg) {
@ -241,47 +298,62 @@ class dba {
}
public function escape($str) {
if($this->db && $this->connected) {
if($this->mysqli)
if ($this->db && $this->connected) {
if ($this->mysqli) {
return @$this->db->real_escape_string($str);
else
} else {
return @mysql_real_escape_string($str,$this->db);
}
}
}
function connected() {
if ($this->mysqli) {
$connected = $this->db->ping();
} else {
$connected = mysql_ping($this->db);
}
return $connected;
}
function __destruct() {
if ($this->db)
if($this->mysqli)
if ($this->db) {
if ($this->mysqli) {
$this->db->close();
else
} else {
mysql_close($this->db);
}
}
}
}}
if(! function_exists('printable')) {
if (! function_exists('printable')) {
function printable($s) {
$s = preg_replace("~([\x01-\x08\x0E-\x0F\x10-\x1F\x7F-\xFF])~",".", $s);
$s = str_replace("\x00",'.',$s);
if(x($_SERVER,'SERVER_NAME'))
if (x($_SERVER,'SERVER_NAME')) {
$s = escape_tags($s);
}
return $s;
}}
// Procedural functions
if(! function_exists('dbg')) {
if (! function_exists('dbg')) {
function dbg($state) {
global $db;
if($db)
$db->dbg($state);
if ($db) {
$db->dbg($state);
}
}}
if(! function_exists('dbesc')) {
if (! function_exists('dbesc')) {
function dbesc($str) {
global $db;
if($db && $db->connected)
if ($db && $db->connected) {
return($db->escape($str));
else
} else {
return(str_replace("'","\\'",$str));
}
}}
@ -291,17 +363,17 @@ function dbesc($str) {
// Example: $r = q("SELECT * FROM `%s` WHERE `uid` = %d",
// 'user', 1);
if(! function_exists('q')) {
if (! function_exists('q')) {
function q($sql) {
global $db;
$args = func_get_args();
unset($args[0]);
if($db && $db->connected) {
if ($db && $db->connected) {
$stmt = @vsprintf($sql,$args); // Disabled warnings
//logger("dba: q: $stmt", LOGGER_ALL);
if($stmt === false)
if ($stmt === false)
logger('dba: vsprintf error: ' . print_r(debug_backtrace(),true), LOGGER_DEBUG);
return $db->q($stmt);
}
@ -317,20 +389,57 @@ function q($sql) {
}}
/**
* @brief Performs a query with "dirty reads"
*
* By doing dirty reads (reading uncommitted data) no locks are performed
* This function can be used to fetch data that doesn't need to be reliable.
*
* @param $args Query parameters (1 to N parameters of different types)
* @return array Query array
*/
function qu($sql) {
global $db;
$args = func_get_args();
unset($args[0]);
if ($db && $db->connected) {
$stmt = @vsprintf($sql,$args); // Disabled warnings
if ($stmt === false)
logger('dba: vsprintf error: ' . print_r(debug_backtrace(),true), LOGGER_DEBUG);
$db->q("SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;");
$retval = $db->q($stmt);
$db->q("SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;");
return $retval;
}
/**
*
* This will happen occasionally trying to store the
* session data after abnormal program termination
*
*/
logger('dba: no database: ' . print_r($args,true));
return false;
}
/**
*
* Raw db query, no arguments
*
*/
if(! function_exists('dbq')) {
if (! function_exists('dbq')) {
function dbq($sql) {
global $db;
if($db && $db->connected)
if ($db && $db->connected) {
$ret = $db->q($sql);
else
} else {
$ret = false;
}
return $ret;
}}
@ -341,16 +450,16 @@ function dbq($sql) {
// cast to int to avoid trouble.
if(! function_exists('dbesc_array_cb')) {
if (! function_exists('dbesc_array_cb')) {
function dbesc_array_cb(&$item, $key) {
if(is_string($item))
if (is_string($item))
$item = dbesc($item);
}}
if(! function_exists('dbesc_array')) {
if (! function_exists('dbesc_array')) {
function dbesc_array(&$arr) {
if(is_array($arr) && count($arr)) {
if (is_array($arr) && count($arr)) {
array_walk($arr,'dbesc_array_cb');
}
}}

157
include/dbclean.php Normal file
View file

@ -0,0 +1,157 @@
<?php
/**
* @file include/dbclean.php
* @brief The script is called from time to time to clean the database entries and remove orphaned data.
*/
require_once("boot.php");
function dbclean_run(&$argv, &$argc) {
global $a, $db;
if (is_null($a))
$a = new App;
if (is_null($db)) {
@include(".htconfig.php");
require_once("include/dba.php");
$db = new dba($db_host, $db_user, $db_pass, $db_data);
unset($db_host, $db_user, $db_pass, $db_data);
}
load_config('config');
load_config('system');
if ($argc == 2) {
$stage = intval($argv[1]);
} else {
$stage = 0;
}
if (get_config("system", "worker") AND ($stage == 0)) {
proc_run(PRIORITY_LOW, 'include/dbclean.php', 1);
proc_run(PRIORITY_LOW, 'include/dbclean.php', 2);
proc_run(PRIORITY_LOW, 'include/dbclean.php', 3);
proc_run(PRIORITY_LOW, 'include/dbclean.php', 4);
proc_run(PRIORITY_LOW, 'include/dbclean.php', 5);
proc_run(PRIORITY_LOW, 'include/dbclean.php', 6);
proc_run(PRIORITY_LOW, 'include/dbclean.php', 7);
} else {
remove_orphans($stage);
}
}
/**
* @brief Remove orphaned database entries
*/
function remove_orphans($stage = 0) {
global $db;
$count = 0;
if (($stage == 1) OR ($stage == 0)) {
logger("Deleting old global item entries from item table without user copy");
if ($db->q("SELECT `id` FROM `item` WHERE `uid` = 0
AND NOT EXISTS (SELECT `guid` FROM `item` AS `i` WHERE `item`.`guid` = `i`.`guid` AND `i`.`uid` != 0)
AND `received` < UTC_TIMESTAMP() - INTERVAL 90 DAY LIMIT 10000", true)) {
$count = $db->num_rows();
logger("found global item orphans: ".$count);
while ($orphan = $db->qfetch()) {
q("DELETE FROM `item` WHERE `id` = %d", intval($orphan["id"]));
}
}
$db->qclose();
logger("Done deleting old global item entries from item table without user copy");
}
if (($stage == 2) OR ($stage == 0)) {
logger("Deleting items without parents");
if ($db->q("SELECT `id` FROM `item` WHERE NOT EXISTS (SELECT `id` FROM `item` AS `i` WHERE `item`.`parent` = `i`.`id`) LIMIT 10000", true)) {
$count = $db->num_rows();
logger("found item orphans without parents: ".$count);
while ($orphan = $db->qfetch()) {
q("DELETE FROM `item` WHERE `id` = %d", intval($orphan["id"]));
}
}
$db->qclose();
logger("Done deleting items without parents");
}
if (($stage == 3) OR ($stage == 0)) {
logger("Deleting orphaned data from thread table");
if ($db->q("SELECT `iid` FROM `thread` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`parent` = `thread`.`iid`)", true)) {
$count = $db->num_rows();
logger("found thread orphans: ".$count);
while ($orphan = $db->qfetch()) {
q("DELETE FROM `thread` WHERE `iid` = %d", intval($orphan["iid"]));
}
}
$db->qclose();
logger("Done deleting orphaned data from thread table");
}
if (($stage == 4) OR ($stage == 0)) {
logger("Deleting orphaned data from notify table");
if ($db->q("SELECT `iid` FROM `notify` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`id` = `notify`.`iid`)", true)) {
$count = $db->num_rows();
logger("found notify orphans: ".$count);
while ($orphan = $db->qfetch()) {
q("DELETE FROM `notify` WHERE `iid` = %d", intval($orphan["iid"]));
}
}
$db->qclose();
logger("Done deleting orphaned data from notify table");
}
if (($stage == 5) OR ($stage == 0)) {
logger("Deleting orphaned data from notify-threads table");
if ($db->q("SELECT `id` FROM `notify-threads` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`parent` = `notify-threads`.`master-parent-item`)", true)) {
$count = $db->num_rows();
logger("found notify-threads orphans: ".$count);
while ($orphan = $db->qfetch()) {
q("DELETE FROM `notify-threads` WHERE `id` = %d", intval($orphan["id"]));
}
}
$db->qclose();
logger("Done deleting orphaned data from notify-threads table");
}
if (($stage == 6) OR ($stage == 0)) {
logger("Deleting orphaned data from sign table");
if ($db->q("SELECT `iid` FROM `sign` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`id` = `sign`.`iid`)", true)) {
$count = $db->num_rows();
logger("found sign orphans: ".$count);
while ($orphan = $db->qfetch()) {
q("DELETE FROM `sign` WHERE `iid` = %d", intval($orphan["iid"]));
}
}
$db->qclose();
logger("Done deleting orphaned data from sign table");
}
if (($stage == 7) OR ($stage == 0)) {
logger("Deleting orphaned data from term table");
if ($db->q("SELECT `oid` FROM `term` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`id` = `term`.`oid`)", true)) {
$count = $db->num_rows();
logger("found term orphans: ".$count);
while ($orphan = $db->qfetch()) {
q("DELETE FROM `term` WHERE `oid` = %d", intval($orphan["oid"]));
}
}
$db->qclose();
logger("Done deleting orphaned data from term table");
}
// Call it again if not all entries were purged
if (($stage != 0) AND ($count > 0) AND get_config("system", "worker")) {
proc_run(PRIORITY_LOW, 'include/dbclean.php');
}
}
if (array_search(__file__,get_included_files())===0){
dbclean_run($_SERVER["argv"],$_SERVER["argc"]);
killme();
}
?>

View file

@ -43,6 +43,10 @@ class dbm {
* @return Whether $array is a filled array
*/
public static function is_result($array) {
// It could be a return value from an update statement
if (is_bool($array)) {
return $array;
}
return (is_array($array) && count($array) > 0);
}
}

View file

@ -78,8 +78,16 @@ function table_structure($table) {
if ($index["Index_type"] == "FULLTEXT")
continue;
if ($index['Key_name'] != 'PRIMARY' && $index['Non_unique'] == '0' && !isset($indexdata[$index["Key_name"]])) {
$indexdata[$index["Key_name"]] = array('UNIQUE');
}
$column = $index["Column_name"];
if ($index["Sub_part"] != "")
// On utf8mb4 a varchar index can only have a length of 191
// To avoid the need to add this to every index definition we just ignore it here.
// Exception are primary indexes
// Since there are some combindex primary indexes we use the limit of 180 here.
if (($index["Sub_part"] != "") AND (($index["Sub_part"] < 180) OR ($index["Key_name"] == "PRIMARY")))
$column .= "(".$index["Sub_part"].")";
$indexdata[$index["Key_name"]][] = $column;
@ -104,7 +112,7 @@ function table_structure($table) {
return(array("fields"=>$fielddata, "indexes"=>$indexdata));
}
function print_structure($database) {
function print_structure($database, $charset) {
echo "-- ------------------------------------------\n";
echo "-- ".FRIENDICA_PLATFORM." ".FRIENDICA_VERSION." (".FRIENDICA_CODENAME,")\n";
echo "-- DB_UPDATE_VERSION ".DB_UPDATE_VERSION."\n";
@ -113,7 +121,7 @@ function print_structure($database) {
echo "--\n";
echo "-- TABLE $name\n";
echo "--\n";
db_create_table($name, $structure['fields'], true, false, $structure["indexes"]);
db_create_table($name, $structure['fields'], $charset, true, false, $structure["indexes"]);
echo "\n";
}
@ -122,6 +130,14 @@ function print_structure($database) {
function update_structure($verbose, $action, $tables=null, $definition=null) {
global $a, $db;
if ($action)
set_config('system', 'maintenance', 1);
if (isset($a->config["system"]["db_charset"]))
$charset = $a->config["system"]["db_charset"];
else
$charset = "utf8";
$errors = false;
logger('updating structure', LOGGER_DEBUG);
@ -140,15 +156,29 @@ function update_structure($verbose, $action, $tables=null, $definition=null) {
// Get the definition
if (is_null($definition))
$definition = db_definition();
$definition = db_definition($charset);
// Ensure index conversion to unique removes duplicates
$sql_config = "SET session old_alter_table=1;";
if ($verbose)
echo $sql_config."\n";
if ($action)
@$db->q($sql_config);
// MySQL >= 5.7.4 doesn't support the IGNORE keyword in ALTER TABLE statements
if ((version_compare($db->server_info(), '5.7.4') >= 0) AND
!(strpos($db->server_info(), 'MariaDB') !== false)) {
$ignore = '';
}else {
$ignore = ' IGNORE';
}
// Compare it
foreach ($definition AS $name => $structure) {
$is_new_table = False;
$sql3="";
if (!isset($database[$name])) {
$r = db_create_table($name, $structure["fields"], $verbose, $action, $structure['indexes']);
$r = db_create_table($name, $structure["fields"], $charset, $verbose, $action, $structure['indexes']);
if(false === $r) {
$errors .= t('Errors encountered creating database tables.').$name.EOL;
}
@ -167,7 +197,7 @@ function update_structure($verbose, $action, $tables=null, $definition=null) {
if ($current_index_definition != $new_index_definition && substr($indexname, 0, 6) != 'local_') {
$sql2=db_drop_index($indexname);
if ($sql3 == "")
$sql3 = "ALTER TABLE `".$name."` ".$sql2;
$sql3 = "ALTER".$ignore." TABLE `".$name."` ".$sql2;
else
$sql3 .= ", ".$sql2;
}
@ -211,7 +241,7 @@ function update_structure($verbose, $action, $tables=null, $definition=null) {
$sql2=db_create_index($indexname, $fieldnames);
if ($sql2 != "") {
if ($sql3 == "")
$sql3 = "ALTER TABLE `".$name."` ".$sql2;
$sql3 = "ALTER" . $ignore . " TABLE `".$name."` ".$sql2;
else
$sql3 .= ", ".$sql2;
}
@ -232,6 +262,9 @@ function update_structure($verbose, $action, $tables=null, $definition=null) {
}
}
if ($action)
set_config('system', 'maintenance', 0);
return $errors;
}
@ -257,16 +290,9 @@ function db_field_command($parameters, $create = true) {
return($fieldstruct);
}
function db_create_table($name, $fields, $verbose, $action, $indexes=null) {
function db_create_table($name, $fields, $charset, $verbose, $action, $indexes=null) {
global $a, $db;
if (isset($a->config["system"]["db_charset"]))
$charset = $a->config["system"]["db_charset"];
elseif ($verbose)
$charset = "utf8mb4";
else
$charset = "utf8";
$r = true;
$sql = "";
@ -322,9 +348,9 @@ function db_create_index($indexname, $fieldnames, $method="ADD") {
killme();
}
if ($indexname == "PRIMARY") {
return sprintf("%s PRIMARY KEY(`%s`)", $method, implode("`,`", $fieldnames));
if ($fieldnames[0] == "UNIQUE") {
array_shift($fieldnames);
$method .= ' UNIQUE';
}
$names = "";
@ -338,12 +364,26 @@ function db_create_index($indexname, $fieldnames, $method="ADD") {
$names .= "`".dbesc($fieldname)."`";
}
if ($indexname == "PRIMARY") {
return sprintf("%s PRIMARY KEY(%s)", $method, $names);
}
$sql = sprintf("%s INDEX `%s` (%s)", $method, dbesc($indexname), $names);
return($sql);
}
function db_definition() {
function db_index_suffix($charset, $reduce = 0) {
if ($charset != "utf8mb4")
return "";
// On utf8mb4 indexes can only have a length of 191
$indexlength = 191 - $reduce;
return "(".$indexlength.")";
}
function db_definition($charset) {
$database = array();
@ -401,8 +441,9 @@ function db_definition() {
"updated" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"),
),
"indexes" => array(
"PRIMARY" => array("k"),
"PRIMARY" => array("k".db_index_suffix($charset)),
"updated" => array("updated"),
"expire_mode_updated" => array("expire_mode", "updated"),
)
);
$database["challenge"] = array(
@ -440,7 +481,7 @@ function db_definition() {
),
"indexes" => array(
"PRIMARY" => array("id"),
"cat_k" => array("cat(30)","k(30)"),
"cat_k" => array("UNIQUE", "cat(30)","k(30)"),
)
);
$database["contact"] = array(
@ -459,6 +500,7 @@ function db_definition() {
"about" => array("type" => "text"),
"keywords" => array("type" => "text"),
"gender" => array("type" => "varchar(32)", "not null" => "1", "default" => ""),
"xmpp" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"attag" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"avatar" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"photo" => array("type" => "text"),
@ -498,6 +540,7 @@ function db_definition() {
"writable" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
"forum" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
"prv" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
"contact-type" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0"),
"hidden" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
"archive" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
"pending" => array("type" => "tinyint(1)", "not null" => "1", "default" => "1"),
@ -515,6 +558,7 @@ function db_definition() {
"indexes" => array(
"PRIMARY" => array("id"),
"uid" => array("uid"),
"addr_uid" => array("addr", "uid"),
"nurl" => array("nurl"),
)
);
@ -543,6 +587,7 @@ function db_definition() {
),
"indexes" => array(
"PRIMARY" => array("id"),
"cmd_item_contact" => array("UNIQUE", "cmd", "item", "contact"),
)
);
$database["event"] = array(
@ -667,6 +712,7 @@ function db_definition() {
"gender" => array("type" => "varchar(32)", "not null" => "1", "default" => ""),
"birthday" => array("type" => "varchar(32)", "not null" => "1", "default" => "0000-00-00"),
"community" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
"contact-type" => array("type" => "tinyint(1)", "not null" => "1", "default" => "-1"),
"hide" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
"nsfw" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
"network" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
@ -872,7 +918,7 @@ function db_definition() {
"ownerid_created" => array("owner-id","created"),
"wall_body" => array("wall","body(6)"),
"uid_visible_moderated_created" => array("uid","visible","moderated","created"),
"uid_uri" => array("uid","uri"),
"uid_uri" => array("uid", "uri"),
"uid_wall_created" => array("uid","wall","created"),
"resource-id" => array("resource-id"),
"uid_type" => array("uid","type"),
@ -993,6 +1039,8 @@ function db_definition() {
"seen" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
"verb" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"otype" => array("type" => "varchar(16)", "not null" => "1", "default" => ""),
"name_cache" => array("type" => "tinytext"),
"msg_cache" => array("type" => "mediumtext")
),
"indexes" => array(
"PRIMARY" => array("id"),
@ -1020,7 +1068,7 @@ function db_definition() {
"created" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"),
),
"indexes" => array(
"PRIMARY" => array("url"),
"PRIMARY" => array("url".db_index_suffix($charset)),
"created" => array("created"),
)
);
@ -1033,7 +1081,7 @@ function db_definition() {
"created" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"),
),
"indexes" => array(
"PRIMARY" => array("url", "guessing", "oembed"),
"PRIMARY" => array("url".db_index_suffix($charset), "guessing", "oembed"),
"created" => array("created"),
)
);
@ -1047,7 +1095,7 @@ function db_definition() {
),
"indexes" => array(
"PRIMARY" => array("id"),
"uid_cat_k" => array("uid","cat(30)","k(30)"),
"uid_cat_k" => array("UNIQUE", "uid","cat(30)","k(30)"),
)
);
$database["photo"] = array(
@ -1077,7 +1125,9 @@ function db_definition() {
),
"indexes" => array(
"PRIMARY" => array("id"),
"uid" => array("uid"),
"uid_contactid" => array("uid", "contact-id"),
"uid_profile" => array("uid", "profile"),
"uid_album_created" => array("uid", "album", "created"),
"resource-id" => array("resource-id"),
"guid" => array("guid"),
)
@ -1164,6 +1214,7 @@ function db_definition() {
"education" => array("type" => "text"),
"contact" => array("type" => "text"),
"homepage" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"xmpp" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"photo" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"thumb" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"publish" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
@ -1229,6 +1280,7 @@ function db_definition() {
"uid" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0"),
"password" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"language" => array("type" => "varchar(16)", "not null" => "1", "default" => ""),
"note" => array("type" => "text"),
),
"indexes" => array(
"PRIMARY" => array("id"),
@ -1311,6 +1363,7 @@ function db_definition() {
"type_term" => array("type","term"),
"uid_otype_type_term_global_created" => array("uid","otype","type","term","global","created"),
"otype_type_term_tid" => array("otype","type","term","tid"),
"uid_otype_type_url" => array("uid","otype","type","url"),
"guid" => array("guid"),
)
);
@ -1400,6 +1453,7 @@ function db_definition() {
"cntunkmail" => array("type" => "int(11)", "not null" => "1", "default" => "10"),
"notify-flags" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "65535"),
"page-flags" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0"),
"account-type" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0"),
"prvnets" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
"pwdreset" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"maxreq" => array("type" => "int(11)", "not null" => "1", "default" => "10"),
@ -1469,12 +1523,33 @@ function dbstructure_run(&$argv, &$argc) {
if ($argc==2) {
switch ($argv[1]) {
case "dryrun":
update_structure(true, false);
return;
case "update":
update_structure(true, true);
$build = get_config('system','build');
if (!x($build)) {
set_config('system','build',DB_UPDATE_VERSION);
$build = DB_UPDATE_VERSION;
}
$stored = intval($build);
$current = intval(DB_UPDATE_VERSION);
// run any left update_nnnn functions in update.php
for($x = $stored; $x < $current; $x ++) {
$r = run_update_function($x);
if (!$r) break;
}
set_config('system','build',DB_UPDATE_VERSION);
return;
case "dumpsql":
print_structure(db_definition());
// For the dump that is used to create the database.sql we always assume utfmb4
$charset = "utf8mb4";
print_structure(db_definition($charset), $charset);
return;
}
}
@ -1483,7 +1558,8 @@ function dbstructure_run(&$argv, &$argc) {
// print help
echo $argv[0]." <command>\n";
echo "\n";
echo "commands:\n";
echo "Commands:\n";
echo "dryrun show database update schema queries without running them\n";
echo "update update database schema\n";
echo "dumpsql dump database schema\n";
return;

View file

@ -178,7 +178,7 @@ function delivery_run(&$argv, &$argc){
$r = q("SELECT `contact`.*, `user`.`pubkey` AS `upubkey`, `user`.`prvkey` AS `uprvkey`,
`user`.`timezone`, `user`.`nickname`, `user`.`sprvkey`, `user`.`spubkey`,
`user`.`page-flags`, `user`.`prvnets`
`user`.`page-flags`, `user`.`account-type`, `user`.`prvnets`
FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
WHERE `contact`.`uid` = %d AND `contact`.`self` = 1 LIMIT 1",
intval($uid)
@ -381,7 +381,14 @@ function delivery_run(&$argv, &$argc){
if ($deliver_status == (-1)) {
logger('notifier: delivery failed: queuing message');
add_to_queue($contact['id'],NETWORK_DFRN,$atom);
// The message could not be delivered. We mark the contact as "dead"
mark_for_death($contact);
} else {
// We successfully delivered a message, the contact is alive
unmark_for_death($contact);
}
break;
case NETWORK_OSTATUS:

View file

@ -3,7 +3,8 @@
* @file include/dfrn.php
* @brief The implementation of the dfrn protocol
*
* https://github.com/friendica/friendica/wiki/Protocol
* @see https://github.com/friendica/friendica/wiki/Protocol and
* https://github.com/friendica/friendica/blob/master/spec/dfrn2.pdf
*/
require_once("include/Contact.php");
@ -98,9 +99,9 @@ class dfrn {
$sql_extra = " AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' ";
$r = q("SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
$r = q("SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`, `user`.`account-type`
FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
WHERE `contact`.`self` AND `user`.`nickname` = '%s' LIMIT 1",
dbesc($owner_nick)
);
@ -112,7 +113,6 @@ class dfrn {
$owner_nick = $owner['nickname'];
$sql_post_table = "";
$visibility = "";
if(! $public_feed) {
@ -135,7 +135,7 @@ class dfrn {
break; // NOTREACHED
}
$r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
$r = q("SELECT * FROM `contact` WHERE NOT `blocked` AND `contact`.`uid` = %d $sql_extra LIMIT 1",
intval($owner_id)
);
@ -171,9 +171,6 @@ class dfrn {
else
$sort = 'ASC';
$date_field = "`changed`";
$sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC";
if(! strlen($last_update))
$last_update = 'now -30 days';
@ -190,22 +187,19 @@ class dfrn {
$check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
// AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
// dbesc($check_date),
$r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`,
$r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
`contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
`contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
`contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
`sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
FROM `item` $sql_post_table
INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
FROM `item` USE INDEX (`uid_wall_changed`, `uid_type_changed`) $sql_post_table
STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
AND (NOT `contact`.`blocked` OR `contact`.`pending`)
LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s'
WHERE `item`.`uid` = %d AND `item`.`visible` AND NOT `item`.`moderated` AND `item`.`parent` != 0
AND `item`.`wall` AND `item`.`changed` > '%s'
$sql_extra
ORDER BY $sql_order LIMIT 0, 300",
ORDER BY `item`.`parent` ".$sort.", `item`.`created` ASC LIMIT 0, 300",
intval($owner_id),
dbesc($check_date),
dbesc($sort)
@ -440,9 +434,13 @@ class dfrn {
xml::add_element($doc, $root, "link", "", $attributes);
}
// For backward compatibility we keep this element
if ($owner['page-flags'] == PAGE_COMMUNITY)
xml::add_element($doc, $root, "dfrn:community", 1);
// The former element is replaced by this one
xml::add_element($doc, $root, "dfrn:account_type", $owner["account-type"]);
/// @todo We need a way to transmit the different page flags like "PAGE_PRVGROUP"
xml::add_element($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME));
@ -512,14 +510,16 @@ class dfrn {
xml::add_element($doc, $author, "dfrn:birthday", $birthday);
// Only show contact details when we are allowed to
$r = q("SELECT `profile`.`about`, `profile`.`name`, `profile`.`homepage`, `user`.`nickname`, `user`.`timezone`,
`profile`.`locality`, `profile`.`region`, `profile`.`country-name`, `profile`.`pub_keywords`, `profile`.`dob`
$r = q("SELECT `profile`.`about`, `profile`.`name`, `profile`.`homepage`, `user`.`nickname`,
`user`.`timezone`, `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
`profile`.`pub_keywords`, `profile`.`xmpp`, `profile`.`dob`
FROM `profile`
INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
WHERE `profile`.`is-default` AND NOT `user`.`hidewall` AND `user`.`uid` = %d",
intval($owner['uid']));
if ($r) {
$profile = $r[0];
xml::add_element($doc, $author, "poco:displayName", $profile["name"]);
xml::add_element($doc, $author, "poco:updated", $namdate);
@ -550,12 +550,10 @@ class dfrn {
}
/// @todo When we are having the XMPP address in the profile we should propagate it here
$xmpp = "";
if (trim($xmpp) != "") {
if (trim($profile["xmpp"]) != "") {
$ims = $doc->createElement("poco:ims");
xml::add_element($doc, $ims, "poco:type", "xmpp");
xml::add_element($doc, $ims, "poco:value", $xmpp);
xml::add_element($doc, $ims, "poco:value", $profile["xmpp"]);
xml::add_element($doc, $ims, "poco:primary", "true");
$author->appendChild($ims);
}
@ -1143,7 +1141,7 @@ class dfrn {
$author["link"] = $xpath->evaluate($element."/atom:uri/text()", $context)->item(0)->nodeValue;
$r = q("SELECT `id`, `uid`, `url`, `network`, `avatar-date`, `name-date`, `uri-date`, `addr`,
`name`, `nick`, `about`, `location`, `keywords`, `bdyear`, `bd`, `hidden`
`name`, `nick`, `about`, `location`, `keywords`, `xmpp`, `bdyear`, `bd`, `hidden`, `contact-type`
FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'",
intval($importer["uid"]), dbesc(normalise_link($author["link"])), dbesc(NETWORK_STATUSNET));
if ($r) {
@ -1219,9 +1217,13 @@ class dfrn {
if ($value != "")
$poco["location"] = $value;
/// @todo Only search for elements with "poco:type" = "xmpp"
$value = $xpath->evaluate($element."/poco:ims/poco:value/text()", $context)->item(0)->nodeValue;
if ($value != "")
$poco["xmpp"] = $value;
/// @todo Add support for the following fields that we don't support by now in the contact table:
/// - poco:utcOffset
/// - poco:ims
/// - poco:urls
/// - poco:locality
/// - poco:region
@ -1308,12 +1310,13 @@ class dfrn {
q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s',
`addr` = '%s', `keywords` = '%s', `bdyear` = '%s', `bd` = '%s', `hidden` = %d,
`name-date` = '%s', `uri-date` = '%s'
`xmpp` = '%s', `name-date` = '%s', `uri-date` = '%s'
WHERE `id` = %d AND `network` = '%s'",
dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["about"]), dbesc($contact["location"]),
dbesc($contact["addr"]), dbesc($contact["keywords"]), dbesc($contact["bdyear"]),
dbesc($contact["bd"]), intval($contact["hidden"]), dbesc($contact["name-date"]),
dbesc($contact["uri-date"]), intval($contact["id"]), dbesc($contact["network"]));
dbesc($contact["bd"]), intval($contact["hidden"]), dbesc($contact["xmpp"]),
dbesc($contact["name-date"]), dbesc($contact["uri-date"]),
intval($contact["id"]), dbesc($contact["network"]));
}
update_contact_avatar($author["avatar"], $importer["uid"], $contact["id"],
@ -1327,6 +1330,7 @@ class dfrn {
$poco["generation"] = 2;
$poco["photo"] = $author["avatar"];
$poco["hide"] = $hide;
$poco["contact-type"] = $contact["contact-type"];
update_gcontact($poco);
}
@ -2483,7 +2487,19 @@ class dfrn {
logger("Import DFRN message for user ".$importer["uid"]." from contact ".$importer["id"], LOGGER_DEBUG);
// is it a public forum? Private forums aren't supported by now with this method
// The account type is new since 3.5.1
if ($xpath->query("/atom:feed/dfrn:account_type")->length > 0) {
$accounttype = intval($xpath->evaluate("/atom:feed/dfrn:account_type/text()", $context)->item(0)->nodeValue);
if ($accounttype != $importer["contact-type"])
q("UPDATE `contact` SET `contact-type` = %d WHERE `id` = %d",
intval($accounttype),
intval($importer["id"])
);
}
// is it a public forum? Private forums aren't supported with this method
// This is deprecated since 3.5.1
$forum = intval($xpath->evaluate("/atom:feed/dfrn:community/text()", $context)->item(0)->nodeValue);
if ($forum != $importer["forum"])

View file

@ -999,17 +999,21 @@ class diaspora {
*/
private function author_contact_by_url($contact, $person, $uid) {
$r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1",
$r = q("SELECT `id`, `network`, `url` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1",
dbesc(normalise_link($person["url"])), intval($uid));
if ($r) {
$cid = $r[0]["id"];
$network = $r[0]["network"];
// We are receiving content from a user that is about to be terminated
// This means the user is vital, so we remove a possible termination date.
unmark_for_death($contact);
} else {
$cid = $contact["id"];
$network = NETWORK_DIASPORA;
}
return (array("cid" => $cid, "network" => $network));
return array("cid" => $cid, "network" => $network);
}
/**
@ -2633,7 +2637,13 @@ class diaspora {
} else {
// queue message for redelivery
add_to_queue($contact["id"], NETWORK_DIASPORA, $slap, $public_batch);
// The message could not be delivered. We mark the contact as "dead"
mark_for_death($contact);
}
} elseif (($return_code >= 200) AND ($return_code <= 299)) {
// We successfully delivered a message, the contact is alive
unmark_for_death($contact);
}
return(($return_code) ? $return_code : (-1));
@ -2876,8 +2886,10 @@ class diaspora {
"created_at" => $created,
"provider_display_name" => $item["app"]);
if (count($location) == 0)
// Diaspora rejects messages when they contain a location without "lat" or "lng"
if (!isset($location["lat"]) OR !isset($location["lng"])) {
unset($message["location"]);
}
$type = "status_message";
}

View file

@ -49,7 +49,7 @@ function notification($params) {
// with $params['show_in_notification_page'] == false, the notification isn't inserted into
// the database, and an email is sent if applicable.
// default, if not specified: true
$show_in_notification_page = ((x($params, 'show_in_notification_page')) ? $params['show_in_notification_page']:True);
$show_in_notification_page = ((x($params, 'show_in_notification_page')) ? $params['show_in_notification_page']:true);
$additional_mail_header = "";
$additional_mail_header .= "Precedence: list\n";
@ -418,6 +418,7 @@ function notification($params) {
$datarray = array();
$datarray['hash'] = $hash;
$datarray['name'] = $params['source_name'];
$datarray['name_cache'] = strip_tags(bbcode($params['source_name']));
$datarray['url'] = $params['source_link'];
$datarray['photo'] = $params['source_photo'];
$datarray['date'] = datetime_convert();
@ -439,8 +440,8 @@ function notification($params) {
// create notification entry in DB
$r = q("INSERT INTO `notify` (`hash`, `name`, `url`, `photo`, `date`, `uid`, `link`, `iid`, `parent`, `type`, `verb`, `otype`)
values('%s', '%s', '%s', '%s', '%s', %d, '%s', %d, %d, %d, '%s', '%s')",
$r = q("INSERT INTO `notify` (`hash`, `name`, `url`, `photo`, `date`, `uid`, `link`, `iid`, `parent`, `type`, `verb`, `otype`, `name_cache`)
values('%s', '%s', '%s', '%s', '%s', %d, '%s', %d, %d, %d, '%s', '%s', '%s')",
dbesc($datarray['hash']),
dbesc($datarray['name']),
dbesc($datarray['url']),
@ -452,7 +453,8 @@ function notification($params) {
intval($datarray['parent']),
intval($datarray['type']),
dbesc($datarray['verb']),
dbesc($datarray['otype'])
dbesc($datarray['otype']),
dbesc($datarray["name_cache"])
);
$r = q("SELECT `id` FROM `notify` WHERE `hash` = '%s' AND `uid` = %d LIMIT 1",
@ -494,8 +496,10 @@ function notification($params) {
$itemlink = $a->get_baseurl().'/notify/view/'.$notify_id;
$msg = replace_macros($epreamble, array('$itemlink' => $itemlink));
$r = q("UPDATE `notify` SET `msg` = '%s' WHERE `id` = %d AND `uid` = %d",
$msg_cache = format_notification_message($datarray['name_cache'], strip_tags(bbcode($msg)));
$r = q("UPDATE `notify` SET `msg` = '%s', `msg_cache` = '%s' WHERE `id` = %d AND `uid` = %d",
dbesc($msg),
dbesc($msg_cache),
intval($notify_id),
intval($params['uid'])
);
@ -778,4 +782,27 @@ function check_item_notification($itemid, $uid, $defaulttype = "") {
if (isset($params["type"]))
notification($params);
}
?>
/**
* @brief Formats a notification message with the notification author
*
* Replace the name with {0} but ensure to make that only once. The {0} is used
* later and prints the name in bold.
*
* @param string $name
* @param string $message
* @return string Formatted message
*/
function format_notification_message($name, $message) {
if ($name != '') {
$pos = strpos($message, $name);
} else {
$pos = false;
}
if ($pos !== false) {
$message = substr_replace($message, '{0}', $pos, strlen($name));
}
return $message;
}

View file

@ -480,6 +480,13 @@ function get_event_strings() {
"month" => t("month"),
"week" => t("week"),
"day" => t("day"),
"allday" => t("all-day"),
"noevent" => t("No events to display"),
"dtstart_label" => t("Starts:"),
"dtend_label" => t("Finishes:"),
"location_label" => t("Location:")
);
return $i18n;
@ -502,7 +509,7 @@ function event_by_id($owner_uid = 0, $event_params, $sql_extra = '') {
// query for the event by event id
$r = q("SELECT `event`.*, `item`.`id` AS `itemid`,`item`.`plink`,
`item`.`author-name`, `item`.`author-avatar`, `item`.`author-link` FROM `event`
LEFT JOIN `item` ON `item`.`event-id` = `event`.`id` AND `item`.`uid` = `event`.`uid`
STRAIGHT_JOIN `item` ON `item`.`event-id` = `event`.`id` AND `item`.`uid` = `event`.`uid`
WHERE `event`.`uid` = %d AND `event`.`id` = %d $sql_extra",
intval($owner_uid),
intval($event_params["event_id"])
@ -535,7 +542,7 @@ function events_by_date($owner_uid = 0, $event_params, $sql_extra = '') {
// query for the event by date
$r = q("SELECT `event`.*, `item`.`id` AS `itemid`,`item`.`plink`,
`item`.`author-name`, `item`.`author-avatar`, `item`.`author-link` FROM `event`
LEFT JOIN `item` ON `item`.`event-id` = `event`.`id` AND `item`.`uid` = `event`.`uid`
STRAIGHT_JOIN `item` ON `item`.`event-id` = `event`.`id` AND `item`.`uid` = `event`.`uid`
WHERE `event`.`uid` = %d AND event.ignore = %d
AND ((`adjust` = 0 AND (`finish` >= '%s' OR (nofinish AND start >= '%s')) AND `start` <= '%s')
OR (`adjust` = 1 AND (`finish` >= '%s' OR (nofinish AND start >= '%s')) AND `start` <= '%s'))

View file

@ -10,17 +10,24 @@
*
* @return boolean
*/
function feature_enabled($uid,$feature) {
function feature_enabled($uid, $feature) {
$x = get_config('feature_lock',$feature);
if($x === false) {
$x = get_pconfig($uid,'feature',$feature);
if($x === false) {
$x = get_config('feature',$feature);
if($x === false)
if (($feature == 'richtext') AND !get_app()->theme_richtext_editor) {
return false;
}
$x = get_config('feature_lock', $feature);
if ($x === false) {
$x = get_pconfig($uid, 'feature', $feature);
if ($x === false) {
$x = get_config('feature', $feature);
if ($x === false) {
$x = get_feature_default($feature);
}
}
}
$arr = array('uid' => $uid, 'feature' => $feature, 'enabled' => $x);
call_hooks('feature_enabled',$arr);
return($arr['enabled']);
@ -72,7 +79,7 @@ function get_features($filtered = true) {
t('Post Composition Features'),
array('richtext', t('Richtext Editor'), t('Enable richtext editor'), false, get_config('feature_lock','richtext')),
array('preview', t('Post Preview'), t('Allow previewing posts and comments before publishing them'), false, get_config('feature_lock','preview')),
array('aclautomention', t('Auto-mention Forums'), t('Add/remove mention when a fourm page is selected/deselected in ACL window.'), false, get_config('feature_lock','aclautomention')),
array('aclautomention', t('Auto-mention Forums'), t('Add/remove mention when a forum page is selected/deselected in ACL window.'), false, get_config('feature_lock','aclautomention')),
),
// Network sidebar widgets
@ -135,6 +142,11 @@ function get_features($filtered = true) {
}
}
// Remove the richtext editor setting if the theme doesn't support it
if (!get_app()->theme_richtext_editor) {
unset($arr['composition'][1]);
}
call_hooks('get_features',$arr);
return $arr;
}

View file

@ -325,6 +325,14 @@ function feed_import($xml,$importer,&$contact, &$hub, $simulate = false) {
logger("Stored feed: ".print_r($item, true), LOGGER_DEBUG);
$notify = item_is_remote_self($contact, $item);
// Distributed items should have a well formatted URI.
// Additionally we have to avoid conflicts with identical URI between imported feeds and these items.
if ($notify) {
unset($item['uri']);
unset($item['parent-uri']);
}
$id = item_store($item, false, $notify);
logger("Feed for contact ".$contact["url"]." stored under id ".$id);

View file

@ -270,7 +270,7 @@ function new_contact($uid,$url,$interactive = false) {
// pull feed and consume it, which should subscribe to the hub.
proc_run(PRIORITY_MEDIUM, "include/onepoll.php", $contact_id, "force");
proc_run(PRIORITY_HIGH, "include/onepoll.php", $contact_id, "force");
// create a follow slap

View file

@ -12,11 +12,11 @@ function gprobe_run(&$argv, &$argc){
}
if(is_null($db)) {
@include(".htconfig.php");
require_once("include/dba.php");
$db = new dba($db_host, $db_user, $db_pass, $db_data);
unset($db_host, $db_user, $db_pass, $db_data);
};
@include(".htconfig.php");
require_once("include/dba.php");
$db = new dba($db_host, $db_user, $db_pass, $db_data);
unset($db_host, $db_user, $db_pass, $db_data);
};
require_once('include/session.php');
require_once('include/datetime.php');
@ -39,14 +39,13 @@ function gprobe_run(&$argv, &$argc){
logger("gprobe start for ".normalise_link($url), LOGGER_DEBUG);
if (!count($r)) {
if (!dbm::is_result($r)) {
// Is it a DDoS attempt?
$urlparts = parse_url($url);
$result = Cache::get("gprobe:".$urlparts["host"]);
if (!is_null($result)) {
$result = unserialize($result);
if (in_array($result["network"], array(NETWORK_FEED, NETWORK_PHANTOM))) {
logger("DDoS attempt detected for ".$urlparts["host"]." by ".$_SERVER["REMOTE_ADDR"].". server data: ".print_r($_SERVER, true), LOGGER_DEBUG);
return;
@ -56,7 +55,7 @@ function gprobe_run(&$argv, &$argc){
$arr = probe_url($url);
if (is_null($result))
Cache::set("gprobe:".$urlparts["host"],serialize($arr));
Cache::set("gprobe:".$urlparts["host"], $arr);
if (!in_array($arr["network"], array(NETWORK_FEED, NETWORK_PHANTOM)))
update_gcontact($arr);
@ -65,7 +64,7 @@ function gprobe_run(&$argv, &$argc){
dbesc(normalise_link($url))
);
}
if(count($r)) {
if(dbm::is_result($r)) {
// Check for accessibility and do a poco discovery
if (poco_last_updated($r[0]['url'], true) AND ($r[0]["network"] == NETWORK_DFRN))
poco_load(0,0,$r[0]['id'], str_replace('/profile/','/poco/',$r[0]['url']));
@ -76,6 +75,6 @@ function gprobe_run(&$argv, &$argc){
}
if (array_search(__file__,get_included_files())===0){
gprobe_run($_SERVER["argv"],$_SERVER["argc"]);
killme();
gprobe_run($_SERVER["argv"],$_SERVER["argc"]);
killme();
}

View file

@ -1,11 +1,14 @@
<?php
/*
html2bbcode.php
Converter for HTML to BBCode
Made by: ike@piratenpartei.de
Originally made for the syncom project: http://wiki.piratenpartei.de/Syncom
https://github.com/annando/Syncom
*/
/**
* @file include/html2bbcode.php
* @brief Converter for HTML to BBCode
*
* Made by: ike@piratenpartei.de
* Originally made for the syncom project: http://wiki.piratenpartei.de/Syncom
* https://github.com/annando/Syncom
*/
require_once("include/xml.php");
function node2bbcode(&$doc, $oldnode, $attributes, $startbb, $endbb)
{
@ -76,15 +79,6 @@ function node2bbcodesub(&$doc, $oldnode, $attributes, $startbb, $endbb)
return($replace);
}
if(!function_exists('deletenode')) {
function deletenode(&$doc, $node)
{
$xpath = new DomXPath($doc);
$list = $xpath->query("//".$node);
foreach ($list as $child)
$child->parentNode->removeChild($child);
}}
function _replace_code_cb($m){
return "<code>".str_replace("\n","<br>\n",$m[1]). "</code>";
}
@ -117,12 +111,12 @@ function html2bbcode($message)
@$doc->loadHTML($message);
deletenode($doc, 'style');
deletenode($doc, 'head');
deletenode($doc, 'title');
deletenode($doc, 'meta');
deletenode($doc, 'xml');
deletenode($doc, 'removeme');
xml::deleteNode($doc, 'style');
xml::deleteNode($doc, 'head');
xml::deleteNode($doc, 'title');
xml::deleteNode($doc, 'meta');
xml::deleteNode($doc, 'xml');
xml::deleteNode($doc, 'removeme');
$xpath = new DomXPath($doc);
$list = $xpath->query("//pre");
@ -239,7 +233,7 @@ function html2bbcode($message)
node2bbcode($doc, 'iframe', array('src'=>'/(.+)/'), '[iframe]$1', '[/iframe]');
node2bbcode($doc, 'code', array(), '[code]', '[/code]');
node2bbcode($doc, 'key', array(), '[code]', '[/code]');
node2bbcode($doc, 'key', array(), '[code]', '[/code]');
$message = $doc->saveHTML();

View file

@ -149,17 +149,23 @@ function get_profiledata_by_nick($nickname, $uid = 0, $profile = 0) {
if($profile) {
$profile_int = intval($profile);
$r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `contact`.`avatar-date` AS picdate, `contact`.`addr`, `user`.* FROM `profile`
INNER JOIN `contact` on `contact`.`uid` = `profile`.`uid` INNER JOIN `user` ON `profile`.`uid` = `user`.`uid`
WHERE `user`.`nickname` = '%s' AND `profile`.`id` = %d AND `contact`.`self` = 1 LIMIT 1",
$r = q("SELECT `contact`.`id` AS `contact_id`, `profile`.`uid` AS `profile_uid`, `profile`.*,
`contact`.`avatar-date` AS picdate, `contact`.`addr`, `user`.*
FROM `profile`
INNER JOIN `contact` on `contact`.`uid` = `profile`.`uid` AND `contact`.`self`
INNER JOIN `user` ON `profile`.`uid` = `user`.`uid`
WHERE `user`.`nickname` = '%s' AND `profile`.`id` = %d LIMIT 1",
dbesc($nickname),
intval($profile_int)
);
}
if((!$r) && (!count($r))) {
$r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `contact`.`avatar-date` AS picdate, `contact`.`addr`, `user`.* FROM `profile`
INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid` INNER JOIN `user` ON `profile`.`uid` = `user`.`uid`
WHERE `user`.`nickname` = '%s' AND `profile`.`is-default` = 1 AND `contact`.`self` = 1 LIMIT 1",
$r = q("SELECT `contact`.`id` AS `contact_id`, `profile`.`uid` AS `profile_uid`, `profile`.*,
`contact`.`avatar-date` AS picdate, `contact`.`addr`, `user`.*
FROM `profile`
INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid` AND `contact`.`self`
INNER JOIN `user` ON `profile`.`uid` = `user`.`uid`
WHERE `user`.`nickname` = '%s' AND `profile`.`is-default` LIMIT 1",
dbesc($nickname)
);
}
@ -310,15 +316,8 @@ function profile_sidebar($profile, $block = 0) {
);
}
// check if profile is a forum
if((intval($profile['page-flags']) == PAGE_COMMUNITY)
|| (intval($profile['page-flags']) == PAGE_PRVGROUP)
|| (isset($profile['forum']) && intval($profile['forum']))
|| (isset($profile['prv']) && intval($profile['prv']))
|| (isset($profile['community']) && intval($profile['community'])))
$account_type = t('Forum');
else
$account_type = "";
// Fetch the account type
$account_type = account_type($profile);
if((x($profile,'address') == 1)
|| (x($profile,'location') == 1)
@ -337,6 +336,8 @@ function profile_sidebar($profile, $block = 0) {
$about = ((x($profile,'about') == 1) ? t('About:') : False);
$xmpp = ((x($profile,'xmpp') == 1) ? t('XMPP:') : False);
if(($profile['hidewall'] || $block) && (! local_user()) && (! remote_user())) {
$location = $pdesc = $gender = $marital = $homepage = $about = False;
}
@ -370,7 +371,7 @@ function profile_sidebar($profile, $block = 0) {
if(count($r))
$updated = date("c", strtotime($r[0]['updated']));
$r = q("SELECT COUNT(*) AS `total` FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 AND `pending` = 0 AND `hidden` = 0 AND `archive` = 0
$r = q("SELECT COUNT(*) AS `total` FROM `contact` WHERE `uid` = %d AND NOT `self` AND NOT `blocked` AND NOT `hidden` AND NOT `archive`
AND `network` IN ('%s', '%s', '%s', '')",
intval($profile['uid']),
dbesc(NETWORK_DFRN),
@ -405,6 +406,7 @@ function profile_sidebar($profile, $block = 0) {
$tpl = get_markup_template('profile_vcard.tpl');
$o .= replace_macros($tpl, array(
'$profile' => $p,
'$xmpp' => $xmpp,
'$connect' => $connect,
'$remoteconnect' => $remoteconnect,
'$subscribe_feed' => $subscribe_feed,
@ -811,7 +813,6 @@ function zrl_init(&$a) {
$result = Cache::get("gprobe:".$urlparts["host"]);
if (!is_null($result)) {
$result = unserialize($result);
if (in_array($result["network"], array(NETWORK_FEED, NETWORK_PHANTOM))) {
logger("DDoS attempt detected for ".$urlparts["host"]." by ".$_SERVER["REMOTE_ADDR"].". server data: ".print_r($_SERVER, true), LOGGER_DEBUG);
return;

File diff suppressed because it is too large Load diff

View file

@ -43,181 +43,170 @@ function nav(&$a) {
call_hooks('page_header', $a->page['nav']);
}
function nav_info(&$a) {
/**
* @brief Prepares a list of navigation links
*
* @param App $a
* @return array Navigation links
* string 'sitelocation' => The webbie (username@site.com)
* array 'nav' => Array of links used in the nav menu
* string 'banner' => Formatted html link with banner image
* array 'userinfo' => Array of user information (name, icon)
*/
function nav_info(App $a)
{
$ssl_state = ((local_user()) ? true : false);
/*
*
* Our network is distributed, and as you visit friends some of the
* sites look exactly the same - it isn't always easy to know where you are.
* Display the current site location as a navigation aid.
*
*/
$myident = ((is_array($a->user) && isset($a->user['nickname'])) ? $a->user['nickname'] . '@' : '');
$sitelocation = $myident . substr($a->get_baseurl($ssl_state),strpos($a->get_baseurl($ssl_state),'//') + 2 );
$sitelocation = $myident . substr($a->get_baseurl($ssl_state), strpos($a->get_baseurl($ssl_state), '//') + 2 );
// nav links: array of array('href', 'text', 'extra css classes', 'title')
$nav = Array();
$nav = array();
/*
* Display login or logout
*/
$nav['usermenu']=array();
// Display login or logout
$nav['usermenu'] = array();
$userinfo = null;
if(local_user()) {
$nav['logout'] = Array('logout',t('Logout'), "", t('End this session'));
if (local_user()) {
$nav['logout'] = array('logout', t('Logout'), '', t('End this session'));
// user menu
$nav['usermenu'][] = Array('profile/' . $a->user['nickname'], t('Status'), "", t('Your posts and conversations'));
$nav['usermenu'][] = Array('profile/' . $a->user['nickname']. '?tab=profile', t('Profile'), "", t('Your profile page'));
$nav['usermenu'][] = Array('photos/' . $a->user['nickname'], t('Photos'), "", t('Your photos'));
$nav['usermenu'][] = Array('videos/' . $a->user['nickname'], t('Videos'), "", t('Your videos'));
$nav['usermenu'][] = Array('events/', t('Events'), "", t('Your events'));
$nav['usermenu'][] = Array('notes/', t('Personal notes'), "", t('Your personal notes'));
$nav['usermenu'][] = array('profile/' . $a->user['nickname'], t('Status'), '', t('Your posts and conversations'));
$nav['usermenu'][] = array('profile/' . $a->user['nickname'] . '?tab=profile', t('Profile'), '', t('Your profile page'));
$nav['usermenu'][] = array('photos/' . $a->user['nickname'], t('Photos'), '', t('Your photos'));
$nav['usermenu'][] = array('videos/' . $a->user['nickname'], t('Videos'), '', t('Your videos'));
$nav['usermenu'][] = array('events/', t('Events'), '', t('Your events'));
$nav['usermenu'][] = array('notes/', t('Personal notes'), '', t('Your personal notes'));
// user info
$r = q("SELECT micro FROM contact WHERE uid=%d AND self=1", intval($a->user['uid']));
$r = q("SELECT `micro` FROM `contact` WHERE `uid` = %d AND `self` = 1", intval($a->user['uid']));
$userinfo = array(
'icon' => (count($r) ? $a->remove_baseurl($r[0]['micro']) : "images/person-48.jpg"),
'icon' => (count($r) ? $a->remove_baseurl($r[0]['micro']) : 'images/person-48.jpg'),
'name' => $a->user['username'],
);
}
else {
$nav['login'] = Array('login',t('Login'), ($a->module == 'login'?'selected':''), t('Sign in'));
} else {
$nav['login'] = array('login', t('Login'), ($a->module == 'login' ? 'selected' : ''), t('Sign in'));
}
/*
* "Home" should also take you home from an authenticated remote profile connection
*/
// "Home" should also take you home from an authenticated remote profile connection
$homelink = get_my_url();
if(! $homelink)
if (! $homelink) {
$homelink = ((x($_SESSION,'visitor_home')) ? $_SESSION['visitor_home'] : '');
}
if(($a->module != 'home') && (! (local_user())))
$nav['home'] = array($homelink, t('Home'), "", t('Home Page'));
if (($a->module != 'home') && (! (local_user()))) {
$nav['home'] = array($homelink, t('Home'), '', t('Home Page'));
}
if(($a->config['register_policy'] == REGISTER_OPEN) && (! local_user()) && (! remote_user()))
$nav['register'] = array('register',t('Register'), "", t('Create an account'));
if (($a->config['register_policy'] == REGISTER_OPEN) && (! local_user()) && (! remote_user())) {
$nav['register'] = array('register', t('Register'), '', t('Create an account'));
}
$help_url = 'help';
if(! get_config('system','hide_help'))
$nav['help'] = array($help_url, t('Help'), "", t('Help and documentation'));
if (! get_config('system', 'hide_help')) {
$nav['help'] = array($help_url, t('Help'), '', t('Help and documentation'));
}
if(count($a->apps)>0)
$nav['apps'] = array('apps', t('Apps'), "", t('Addon applications, utilities, games'));
if (count($a->apps) > 0) {
$nav['apps'] = array('apps', t('Apps'), '', t('Addon applications, utilities, games'));
}
if (local_user() OR !get_config('system','local_search')) {
$nav['search'] = array('search', t('Search'), "", t('Search site content'));
if (local_user() OR !get_config('system', 'local_search')) {
$nav['search'] = array('search', t('Search'), '', t('Search site content'));
$nav['searchoption'] = array(
t("Full Text"),
t("Tags"),
t("Contacts"));
t('Full Text'),
t('Tags'),
t('Contacts'));
if (get_config('system','poco_local_search'))
$nav['searchoption'][] = t("Forums");
if (get_config('system', 'poco_local_search')) {
$nav['searchoption'][] = t('Forums');
}
}
$gdirpath = 'directory';
if(strlen(get_config('system','singleuser'))) {
$gdir = get_config('system','directory');
if(strlen($gdir))
$gdirpath = $gdir;
if (strlen(get_config('system', 'singleuser'))) {
$gdir = get_config('system', 'directory');
if(strlen($gdir)) {
$gdirpath = zrl($gdir, true);
}
} elseif (get_config('system', 'community_page_style') == CP_USERS_ON_SERVER) {
$nav['community'] = array('community', t('Community'), '', t('Conversations on this site'));
} elseif (get_config('system', 'community_page_style') == CP_GLOBAL_COMMUNITY) {
$nav['community'] = array('community', t('Community'), '', t('Conversations on the network'));
}
elseif(get_config('system','community_page_style') == CP_USERS_ON_SERVER)
$nav['community'] = array('community', t('Community'), "", t('Conversations on this site'));
elseif(get_config('system','community_page_style') == CP_GLOBAL_COMMUNITY)
$nav['community'] = array('community', t('Community'), "", t('Conversations on the network'));
if(local_user())
$nav['events'] = Array('events', t('Events'), "", t('Events and Calendar'));
if (local_user()) {
$nav['events'] = array('events', t('Events'), '', t('Events and Calendar'));
}
$nav['directory'] = array($gdirpath, t('Directory'), "", t('People directory'));
$nav['directory'] = array($gdirpath, t('Directory'), '', t('People directory'));
$nav['about'] = Array('friendica', t('Information'), "", t('Information about this friendica instance'));
$nav['about'] = array('friendica', t('Information'), '', t('Information about this friendica instance'));
/*
*
* The following nav links are only show to logged in users
*
*/
// The following nav links are only show to logged in users
if (local_user()) {
$nav['network'] = array('network', t('Network'), '', t('Conversations from your friends'));
$nav['net_reset'] = array('network/0?f=&order=comment&nets=all', t('Network Reset'), '', t('Load Network page with no filters'));
if(local_user()) {
$nav['home'] = array('profile/' . $a->user['nickname'], t('Home'), '', t('Your posts and conversations'));
$nav['network'] = array('network', t('Network'), "", t('Conversations from your friends'));
$nav['net_reset'] = array('network/0?f=&order=comment&nets=all', t('Network Reset'), "", t('Load Network page with no filters'));
$nav['home'] = array('profile/' . $a->user['nickname'], t('Home'), "", t('Your posts and conversations'));
if(in_array($_SESSION['page_flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE, PAGE_PRVGROUP))) {
/* only show friend requests for normal pages. Other page types have automatic friendship. */
if(in_array($_SESSION['page_flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_PRVGROUP)))
$nav['introductions'] = array('notifications/intros', t('Introductions'), "", t('Friend Requests'));
if(in_array($_SESSION['page_flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
$nav['notifications'] = array('notifications', t('Notifications'), "", t('Notifications'));
$nav['notifications']['all']=array('notifications/system', t('See all notifications'), "", "");
$nav['notifications']['mark'] = array('', t('Mark as seen'), '',t('Mark all system notifications seen'));
if (in_array($_SESSION['page_flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE, PAGE_PRVGROUP))) {
// only show friend requests for normal pages. Other page types have automatic friendship.
if (in_array($_SESSION['page_flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_PRVGROUP))) {
$nav['introductions'] = array('notifications/intros', t('Introductions'), '', t('Friend Requests'));
}
if (in_array($_SESSION['page_flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
$nav['notifications'] = array('notifications', t('Notifications'), '', t('Notifications'));
$nav['notifications']['all'] = array('notifications/system', t('See all notifications'), '', '');
$nav['notifications']['mark'] = array('', t('Mark as seen'), '', t('Mark all system notifications seen'));
}
}
$nav['messages'] = array('message', t('Messages'), "", t('Private mail'));
$nav['messages']['inbox'] = array('message', t('Inbox'), "", t('Inbox'));
$nav['messages']['outbox']= array('message/sent', t('Outbox'), "", t('Outbox'));
$nav['messages']['new'] = array('message/new', t('New Message'), "", t('New Message'));
$nav['messages'] = array('message', t('Messages'), '', t('Private mail'));
$nav['messages']['inbox'] = array('message', t('Inbox'), '', t('Inbox'));
$nav['messages']['outbox'] = array('message/sent', t('Outbox'), '', t('Outbox'));
$nav['messages']['new'] = array('message/new', t('New Message'), '', t('New Message'));
if(is_array($a->identities) && count($a->identities) > 1) {
$nav['manage'] = array('manage', t('Manage'), "", t('Manage other pages'));
if (is_array($a->identities) && count($a->identities) > 1) {
$nav['manage'] = array('manage', t('Manage'), '', t('Manage other pages'));
}
$nav['delegations'] = Array('delegate', t('Delegations'), "", t('Delegate Page Management'));
$nav['delegations'] = array('delegate', t('Delegations'), '', t('Delegate Page Management'));
$nav['settings'] = array('settings', t('Settings'),"", t('Account settings'));
$nav['settings'] = array('settings', t('Settings'), '', t('Account settings'));
if(feature_enabled(local_user(),'multi_profiles'))
$nav['profiles'] = array('profiles', t('Profiles'),"", t('Manage/Edit Profiles'));
if (feature_enabled(local_user(), 'multi_profiles')) {
$nav['profiles'] = array('profiles', t('Profiles'), '', t('Manage/Edit Profiles'));
}
$nav['contacts'] = array('contacts', t('Contacts'),"", t('Manage/edit friends and contacts'));
$nav['contacts'] = array('contacts', t('Contacts'), '', t('Manage/edit friends and contacts'));
}
/*
* Admin page
*/
if (is_site_admin()){
$nav['admin'] = array('admin/', t('Admin'), "", t('Site setup and configuration'));
}
// Show the link to the admin configuration page if user is admin
if (is_site_admin()) {
$nav['admin'] = array('admin/', t('Admin'), '', t('Site setup and configuration'));
}
$nav['navigation'] = array('navigation/', t('Navigation'), '', t('Site map'));
$nav['navigation'] = array('navigation/', t('Navigation'), "", t('Site map'));
/*
*
* Provide a banner/logo/whatever
*
*/
$banner = get_config('system','banner');
if($banner === false)
$banner .= '<a href="http://friendica.com"><img id="logo-img" src="images/friendica-32.png" alt="logo" /></a><span id="logo-text"><a href="http://friendica.com">Friendica</a></span>';
// Provide a banner/logo/whatever
$banner = get_config('system', 'banner');
if ($banner === false) {
$banner = '<a href="http://friendica.com"><img id="logo-img" src="images/friendica-32.png" alt="logo" /></a><span id="logo-text"><a href="http://friendica.com">Friendica</a></span>';
}
call_hooks('nav_info', $nav);
return array(
'sitelocation' => $sitelocation,
'nav' => $nav,
@ -226,7 +215,6 @@ function nav_info(&$a) {
);
}
/**
* Set a menu item in navbar as selected
*

View file

@ -789,12 +789,12 @@ function short_link($url) {
$yourls->set('password', $yourls_password);
$yourls->set('ssl', $yourls_ssl);
$yourls->set('yourls-url', $yourls_url);
$slinky->set_cascade( array($yourls, new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL()));
$slinky->set_cascade(array($yourls, new Slinky_Ur1ca(), new Slinky_TinyURL()));
} else {
// setup a cascade of shortening services
// try to get a short link from these services
// in the order ur1.ca, trim, id.gd, tinyurl
$slinky->set_cascade(array(new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL()));
// in the order ur1.ca, tinyurl
$slinky->set_cascade(array(new Slinky_Ur1ca(), new Slinky_TinyURL()));
}
return $slinky->short();
}

View file

@ -134,7 +134,7 @@ function notifier_run(&$argv, &$argc){
} elseif($cmd === 'removeme') {
$r = q("SELECT `contact`.*, `user`.`pubkey` AS `upubkey`, `user`.`prvkey` AS `uprvkey`,
`user`.`timezone`, `user`.`nickname`, `user`.`sprvkey`, `user`.`spubkey`,
`user`.`page-flags`, `user`.`prvnets`, `user`.`guid`
`user`.`page-flags`, `user`.`prvnets`, `user`.`account-type`, `user`.`guid`
FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
WHERE `contact`.`uid` = %d AND `contact`.`self` LIMIT 1",
intval($item_id));
@ -204,7 +204,7 @@ function notifier_run(&$argv, &$argc){
$r = q("SELECT `contact`.*, `user`.`pubkey` AS `upubkey`, `user`.`prvkey` AS `uprvkey`,
`user`.`timezone`, `user`.`nickname`, `user`.`sprvkey`, `user`.`spubkey`,
`user`.`page-flags`, `user`.`prvnets`
`user`.`page-flags`, `user`.`prvnets`, `user`.`account-type`
FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
WHERE `contact`.`uid` = %d AND `contact`.`self` = 1 LIMIT 1",
intval($uid)
@ -599,10 +599,10 @@ function notifier_run(&$argv, &$argc){
foreach($r as $rr) {
if((! $mail) && (! $fsuggest) && (! $followup)) {
q("insert into deliverq ( `cmd`,`item`,`contact` ) values ('%s', %d, %d )",
dbesc($cmd),
intval($item_id),
intval($rr['id'])
q("INSERT INTO `deliverq` (`cmd`,`item`,`contact`) VALUES ('%s', %d, %d)
ON DUPLICATE KEY UPDATE `cmd` = '%s', `item` = %d, `contact` = %d",
dbesc($cmd), intval($item_id), intval($rr['id']),
dbesc($cmd), intval($item_id), intval($rr['id'])
);
}
}

View file

@ -1,12 +1,29 @@
<?php
/**
* @file include/oembed.php
*/
use \Friendica\ParseUrl;
use \Friendica\Core\Config;
function oembed_replacecb($matches){
$embedurl=$matches[1];
$j = oembed_fetch_url($embedurl);
$s = oembed_format_object($j);
return $s;
}
/**
* @brief Get data from an URL to embed its content.
*
* @param string $embedurl The URL from which the data should be fetched.
* @param bool $no_rich_type If set to true rich type content won't be fetched.
*
* @return bool|object Returns object with embed content or false if no embedable
* content exists
*/
function oembed_fetch_url($embedurl, $no_rich_type = false){
$embedurl = trim($embedurl, "'");
$embedurl = trim($embedurl, '"');
@ -16,11 +33,11 @@ function oembed_fetch_url($embedurl, $no_rich_type = false){
$r = q("SELECT * FROM `oembed` WHERE `url` = '%s'",
dbesc(normalise_link($embedurl)));
if ($r)
if (dbm::is_result($r)) {
$txt = $r[0]["content"];
else
} else {
$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
@ -28,27 +45,27 @@ function oembed_fetch_url($embedurl, $no_rich_type = false){
$ext = pathinfo(strtolower($embedurl),PATHINFO_EXTENSION);
if(is_null($txt)){
if (is_null($txt)) {
$txt = "";
if (!in_array($ext, $noexts)){
// try oembed autodiscovery
$redirects = 0;
$html_text = fetch_url($embedurl, false, $redirects, 15, "text/*"); /**/
if($html_text){
$html_text = fetch_url($embedurl, false, $redirects, 15, "text/*");
if ($html_text) {
$dom = @DOMDocument::loadHTML($html_text);
if ($dom){
if ($dom) {
$xpath = new DOMXPath($dom);
$attr = "oembed";
$xattr = oe_build_xpath("class","oembed");
$entries = $xpath->query("//link[@type='application/json+oembed']");
foreach($entries as $e){
foreach ($entries as $e) {
$href = $e->getAttributeNode("href")->nodeValue;
$txt = fetch_url($href . '&maxwidth=' . $a->videowidth);
break;
}
$entries = $xpath->query("//link[@type='text/json+oembed']");
foreach($entries as $e){
foreach ($entries as $e) {
$href = $e->getAttributeNode("href")->nodeValue;
$txt = fetch_url($href . '&maxwidth=' . $a->videowidth);
break;
@ -57,8 +74,8 @@ function oembed_fetch_url($embedurl, $no_rich_type = false){
}
}
if ($txt==false || $txt==""){
$embedly = get_config("system", "embedly");
if ($txt==false || $txt=="") {
$embedly = Config::get("system", "embedly");
if ($embedly != "") {
// try embedly service
$ourl = "https://api.embed.ly/1/oembed?key=".$embedly."&url=".urlencode($embedurl);
@ -70,37 +87,39 @@ function oembed_fetch_url($embedurl, $no_rich_type = false){
$txt=trim($txt);
if ($txt[0]!="{")
if ($txt[0]!="{") {
$txt='{"type":"error"}';
else { //save in cache
} else { //save in cache
$j = json_decode($txt);
if ($j->type != "error")
if ($j->type != "error") {
q("INSERT INTO `oembed` (`url`, `content`, `created`) VALUES ('%s', '%s', '%s')
ON DUPLICATE KEY UPDATE `content` = '%s', `created` = '%s'",
dbesc(normalise_link($embedurl)),
dbesc($txt), dbesc(datetime_convert()),
dbesc($txt), dbesc(datetime_convert()));
}
Cache::set($a->videowidth.$embedurl,$txt, CACHE_DAY);
Cache::set($a->videowidth.$embedurl, $txt, CACHE_DAY);
}
}
$j = json_decode($txt);
if (!is_object($j))
if (!is_object($j)) {
return false;
}
// Always embed the SSL version
if (isset($j->html))
if (isset($j->html)) {
$j->html = str_replace(array("http://www.youtube.com/", "http://player.vimeo.com/"),
array("https://www.youtube.com/", "https://player.vimeo.com/"), $j->html);
}
$j->embedurl = $embedurl;
// If fetching information doesn't work, then improve via internal functions
if (($j->type == "error") OR ($no_rich_type AND ($j->type == "rich"))) {
require_once("mod/parse_url.php");
$data = parseurl_getsiteinfo_cached($embedurl, true, false);
$data = ParseUrl::getSiteinfoCached($embedurl, true, false);
$j->type = $data["type"];
if ($j->type == "photo") {
@ -109,16 +128,18 @@ function oembed_fetch_url($embedurl, $no_rich_type = false){
//$j->height = $data["images"][0]["height"];
}
if (isset($data["title"]))
$j->title = $data["title"];
if (isset($data["title"])) {
$j->title = $data["title"];
}
if (isset($data["text"]))
$j->description = $data["text"];
if (isset($data["text"])) {
$j->description = $data["text"];
}
if (is_array($data["images"])) {
$j->thumbnail_url = $data["images"][0]["src"];
$j->thumbnail_width = $data["images"][0]["width"];
$j->thumbnail_height = $data["images"][0]["height"];
$j->thumbnail_url = $data["images"][0]["src"];
$j->thumbnail_width = $data["images"][0]["width"];
$j->thumbnail_height = $data["images"][0]["height"];
}
}
@ -130,12 +151,11 @@ function oembed_fetch_url($embedurl, $no_rich_type = false){
function oembed_format_object($j){
require_once("mod/proxy.php");
$a = get_app();
$embedurl = $j->embedurl;
$jhtml = oembed_iframe($j->embedurl,(isset($j->width) ? $j->width : null), (isset($j->height) ? $j->height : null) );
$ret="<span class='oembed ".$j->type."'>";
switch ($j->type) {
case "video": {
case "video":
if (isset($j->thumbnail_url)) {
$tw = (isset($j->thumbnail_width) && intval($j->thumbnail_width)) ? $j->thumbnail_width:200;
$th = (isset($j->thumbnail_height) && intval($j->thumbnail_height)) ? $j->thumbnail_height:180;
@ -145,7 +165,7 @@ function oembed_format_object($j){
$th=120; $tw = $th*$tr;
$tpl=get_markup_template('oembed_video.tpl');
$ret.=replace_macros($tpl, array(
'$baseurl' => $a->get_baseurl(),
'$baseurl' => App::get_baseurl(),
'$embedurl'=>$embedurl,
'$escapedhtml'=>base64_encode($jhtml),
'$tw'=>$tw,
@ -157,43 +177,49 @@ function oembed_format_object($j){
$ret=$jhtml;
}
//$ret.="<br>";
}; break;
case "photo": {
break;
case "photo":
$ret.= "<img width='".$j->width."' src='".proxy_url($j->url)."'>";
}; break;
case "link": {
}; break;
case "rich": {
break;
case "link":
break;
case "rich":
// not so safe..
if (!get_config("system","no_oembed_rich_content"))
if (!Config::get("system","no_oembed_rich_content")) {
$ret.= proxy_parse_html($jhtml);
}; break;
}
break;
}
// add link to source if not present in "rich" type
if ($j->type!='rich' || !strpos($j->html,$embedurl) ){
$ret .= "<h4>";
if (isset($j->title)) {
if (isset($j->provider_name))
if (isset($j->provider_name)) {
$ret .= $j->provider_name.": ";
}
$embedlink = (isset($j->title))?$j->title:$embedurl;
$ret .= "<a href='$embedurl' rel='oembed'>$embedlink</a>";
if (isset($j->author_name))
if (isset($j->author_name)) {
$ret.=" (".$j->author_name.")";
}
} elseif (isset($j->provider_name) OR isset($j->author_name)) {
$embedlink = "";
if (isset($j->provider_name))
if (isset($j->provider_name)) {
$embedlink .= $j->provider_name;
}
if (isset($j->author_name)) {
if ($embedlink != "")
if ($embedlink != "") {
$embedlink .= ": ";
}
$embedlink .= $j->author_name;
}
if (trim($embedlink) == "")
if (trim($embedlink) == "") {
$embedlink = $embedurl;
}
$ret .= "<a href='$embedurl' rel='oembed'>$embedlink</a>";
}
@ -209,31 +235,39 @@ function oembed_format_object($j){
return mb_convert_encoding($ret, 'HTML-ENTITIES', mb_detect_encoding($ret));
}
function oembed_iframe($src,$width,$height) {
if(! $width || strstr($width,'%'))
$width = '640';
if(! $height || strstr($height,'%')) {
$height = '300';
$resize = 'onload="resizeIframe(this);"';
} else
$resize = '';
// try and leave some room for the description line.
$height = intval($height) + 80;
$width = intval($width) + 40;
$a = get_app();
$s = $a->get_baseurl()."/oembed/".base64url_encode($src);
return '<iframe '.$resize.' class="embed_rich" height="'.$height.'" width="'.$width.'" src="'.$s.'" frameborder="no">'.t('Embedded content').'</iframe>';
/**
* @brief Generates the iframe HTML for an oembed attachment.
*
* Width and height are given by the remote, and are regularly too small for
* the generated iframe.
*
* The width is entirely discarded for the actual width of the post, while fixed
* height is used as a starting point before the inevitable resizing.
*
* Since the iframe is automatically resized on load, there are no need for ugly
* and impractical scrollbars.
*
* @param string $src Original remote URL to embed
* @param string $width
* @param string $height
* @return string formatted HTML
*
* @see oembed_format_object()
*/
function oembed_iframe($src, $width, $height) {
if (!$height || strstr($height,'%')) {
$height = '200';
}
$width = '100%';
$s = App::get_baseurl() . '/oembed/'.base64url_encode($src);
return '<iframe onload="resizeIframe(this);" class="embed_rich" height="' . $height . '" width="' . $width . '" src="' . $s . '" allowfullscreen scrolling="no" frameborder="no">' . t('Embedded content') . '</iframe>';
}
function oembed_bbcode2html($text){
$stopoembed = get_config("system","no_oembed");
$stopoembed = Config::get("system","no_oembed");
if ($stopoembed == true){
return preg_replace("/\[embed\](.+?)\[\/embed\]/is", "<!-- oembed $1 --><i>". t('Embedding disabled') ." : $1</i><!-- /oembed $1 -->" ,$text);
}
@ -246,13 +280,13 @@ function oe_build_xpath($attr, $value){
return "contains( normalize-space( @$attr ), ' $value ' ) or substring( normalize-space( @$attr ), 1, string-length( '$value' ) + 1 ) = '$value ' or substring( normalize-space( @$attr ), string-length( @$attr ) - string-length( '$value' ) ) = ' $value' or @$attr = '$value'";
}
function oe_get_inner_html( $node ) {
$innerHTML= '';
$children = $node->childNodes;
foreach ($children as $child) {
$innerHTML .= $child->ownerDocument->saveXML( $child );
}
return $innerHTML;
function oe_get_inner_html($node) {
$innerHTML= '';
$children = $node->childNodes;
foreach ($children as $child) {
$innerHTML .= $child->ownerDocument->saveXML($child);
}
return $innerHTML;
}
/**
@ -261,15 +295,16 @@ function oe_get_inner_html( $node ) {
*/
function oembed_html2bbcode($text) {
// start parser only if 'oembed' is in text
if (strpos($text, "oembed")){
if (strpos($text, "oembed")) {
// convert non ascii chars to html entities
$html_text = mb_convert_encoding($text, 'HTML-ENTITIES', mb_detect_encoding($text));
// If it doesn't parse at all, just return the text.
$dom = @DOMDocument::loadHTML($html_text);
if(! $dom)
if (! $dom) {
return $text;
}
$xpath = new DOMXPath($dom);
$attr = "oembed";

View file

@ -443,7 +443,7 @@ function onepoll_run(&$argv, &$argc){
$refs_arr[$x] = "'" . msgid2iri(str_replace(array('<','>',' '),array('','',''),dbesc($refs_arr[$x]))) . "'";
}
$qstr = implode(',',$refs_arr);
$r = q("SELECT `uri` , `parent-uri` FROM `item` WHERE `uri` IN ( $qstr ) AND `uid` = %d LIMIT 1",
$r = q("SELECT `uri` , `parent-uri` FROM `item` USE INDEX (`uid_uri`) WHERE `uri` IN ($qstr) AND `uid` = %d LIMIT 1",
intval($importer_uid)
);
if(count($r))
@ -475,9 +475,10 @@ function onepoll_run(&$argv, &$argc){
// If it seems to be a reply but a header couldn't be found take the last message with matching subject
if(!x($datarray,'parent-uri') and $reply) {
$r = q("SELECT `uri` , `parent-uri` FROM `item` WHERE `title` = \"%s\" AND `uid` = %d ORDER BY `created` DESC LIMIT 1",
$r = q("SELECT `uri` , `parent-uri` FROM `item` WHERE `title` = \"%s\" AND `uid` = %d AND `network` = '%s' ORDER BY `created` DESC LIMIT 1",
dbesc(protect_sprintf($datarray['title'])),
intval($importer_uid));
intval($importer_uid),
dbesc(NETWORK_MAIL));
if(count($r))
$datarray['parent-uri'] = $r[0]['parent-uri'];
}

View file

@ -806,11 +806,20 @@ class ostatus {
}
// Get the parent
$parents = q("SELECT `item`.`id`, `item`.`parent`, `item`.`uri`, `item`.`contact-id`, `item`.`type`,
`item`.`verb`, `item`.`visible` FROM `term`
STRAIGHT_JOIN `item` AS `thritem` ON `thritem`.`parent` = `term`.`oid`
STRAIGHT_JOIN `item` ON `item`.`parent` = `thritem`.`parent`
WHERE `term`.`uid` = %d AND `term`.`otype` = %d AND `term`.`type` = %d AND `term`.`url` = '%s'",
intval($uid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION), dbesc($conversation_url));
/* 2016-10-23: The old query will be kept until we are sure that the query above is a good and fast replacement
$parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN
(SELECT `parent` FROM `item` WHERE `id` IN
(SELECT `oid` FROM `term` WHERE `uid` = %d AND `otype` = %d AND `type` = %d AND `url` = '%s'))",
intval($uid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION), dbesc($conversation_url));
*/
if ($parents)
$parent = $parents[0];
elseif (count($item) > 0) {
@ -1961,9 +1970,23 @@ class ostatus {
$last_update = 'now -30 days';
$check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
$authorid = get_contact($owner["url"], 0);
$items = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id` FROM `item`
INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent`
$items = q("SELECT `item`.*, `item`.`id` AS `item_id` FROM `item` USE INDEX (`uid_contactid_created`)
STRAIGHT_JOIN `thread` ON `thread`.`iid` = `item`.`parent`
WHERE `item`.`uid` = %d AND `item`.`contact-id` = %d AND
`item`.`author-id` = %d AND `item`.`created` > '%s' AND
NOT `item`.`deleted` AND NOT `item`.`private` AND
`thread`.`network` IN ('%s', '%s')
ORDER BY `item`.`created` DESC LIMIT 300",
intval($owner["uid"]), intval($owner["id"]),
intval($authorid), dbesc($check_date),
dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
/* 2016-10-23: The old query will be kept until we are sure that the query above is a good and fast replacement
$items = q("SELECT `item`.*, `item`.`id` AS `item_id` FROM `item`
STRAIGHT_JOIN `thread` ON `thread`.`iid` = `item`.`parent`
LEFT JOIN `item` AS `thritem` ON `thritem`.`uri`=`item`.`thr-parent` AND `thritem`.`uid`=`item`.`uid`
WHERE `item`.`uid` = %d AND `item`.`received` > '%s' AND NOT `item`.`private` AND NOT `item`.`deleted`
AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
@ -1981,7 +2004,7 @@ class ostatus {
dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"])),
dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"]))
);
*/
$doc = new DOMDocument('1.0', 'utf-8');
$doc->formatOutput = true;

View file

@ -1,13 +1,7 @@
<?php
require_once("include/dba.php");
/**
* translation support
*/
/**
* @brief translation support
*
* Get the language setting directly from system variables, bypassing get_config()
* as database may not yet be configured.
@ -16,8 +10,12 @@ require_once("include/dba.php");
*
*/
require_once("include/dba.php");
if(! function_exists('get_browser_language')) {
/**
* @brief get the prefered language from the HTTP_ACCEPT_LANGUAGE header
*/
function get_browser_language() {
if (x($_SERVER,'HTTP_ACCEPT_LANGUAGE')) {
@ -25,32 +23,34 @@ function get_browser_language() {
preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i',
$_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse);
$lang_list = [];
if (count($lang_parse[1])) {
// create a list like "en" => 0.8
$langs = array_combine($lang_parse[1], $lang_parse[4]);
// set default to 1 for any without q factor
foreach ($langs as $lang => $val) {
if ($val === '') $langs[$lang] = 1;
}
// sort list based on value
arsort($langs, SORT_NUMERIC);
}
}
if(isset($langs) && count($langs)) {
foreach ($langs as $lang => $v) {
if(file_exists("view/$lang") && is_dir("view/$lang")) {
$preferred = $lang;
break;
// go through the list of prefered languages and add a generic language
// for sub-linguas (e.g. de-ch will add de) if not already in array
for ($i=0; $i<count($lang_parse[1]); $i++) {
$lang_list[] = strtolower($lang_parse[1][$i]);
if ( strlen($lang_parse[1][$i])>3 ) {
$dashpos = strpos($lang_parse[1][$i], '-');
if (! in_array(substr($lang_parse[1][$i], 0, $dashpos), $lang_list ) ) {
$lang_list[] = strtolower(substr($lang_parse[1][$i], 0, $dashpos));
}
}
}
}
}
// check if we have translations for the preferred languages and pick the 1st that has
for ($i=0; $i<count($lang_list); $i++) {
$lang = $lang_list[$i];
if(file_exists("view/lang/$lang") && is_dir("view/lang/$lang")) {
$preferred = $lang;
break;
}
}
if(isset($preferred))
return $preferred;
// in case none matches, get the system wide configured language, or fall back to English
$a = get_app();
return ((isset($a->config['system']['language'])) ? $a->config['system']['language'] : 'en');
}}
@ -112,8 +112,8 @@ function load_translation_table($lang) {
}
}
if(file_exists("view/$lang/strings.php")) {
include("view/$lang/strings.php");
if(file_exists("view/lang/$lang/strings.php")) {
include("view/lang/$lang/strings.php");
}
}}
@ -162,25 +162,31 @@ function string_plural_select_default($n) {
}}
/**
* Return installed languages as associative array
* [
* lang => lang,
* ...
* ]
*/
function get_avaiable_languages() {
$lang_choices = array();
$langs = glob('view/*/strings.php'); /**/
if(is_array($langs) && count($langs)) {
if(! in_array('view/en/strings.php',$langs))
$langs[] = 'view/en/';
asort($langs);
foreach($langs as $l) {
$t = explode("/",$l);
$lang_choices[$t[1]] = $t[1];
/**
* @brief Return installed languages codes as associative array
*
* Scans the view/lang directory for the existence of "strings.php" files, and
* returns an alphabetical list of their folder names (@-char language codes).
* Adds the english language if it's missing from the list.
*
* Ex: array('de' => 'de', 'en' => 'en', 'fr' => 'fr', ...)
*
* @return array
*/
function get_available_languages() {
$langs = array();
$strings_file_paths = glob('view/lang/*/strings.php');
if (is_array($strings_file_paths) && count($strings_file_paths)) {
if (!in_array('view/lang/en/strings.php', $strings_file_paths)) {
$strings_file_paths[] = 'view/lang/en/strings.php';
}
asort($strings_file_paths);
foreach($strings_file_paths as $strings_file_path) {
$path_array = explode('/', $strings_file_path);
$langs[$path_array[2]] = $path_array[2];
}
}
return $lang_choices;
return $langs;
}

View file

@ -25,3 +25,34 @@ function gps2Num($coordPart) {
return floatval($parts[0]) / floatval($parts[1]);
}
/**
* @brief Fetch the photo albums that are available for a viewer
*
* The query in this function is cost intensive, so it is cached.
*
* @param int $uid User id of the photos
* @param bool $update Update the cache
*
* @return array Returns array of the photo albums
*/
function photo_albums($uid, $update = false) {
$sql_extra = permissions_sql($uid);
$key = "photo_albums:".$uid.":".local_user().":".remote_user();
$albums = Cache::get($key);
if (is_null($albums) OR $update) {
/// @todo This query needs to be renewed. It is really slow
// At this time we just store the data in the cache
$albums = qu("SELECT COUNT(DISTINCT `resource-id`) AS `total`, `album`
FROM `photo` USE INDEX (`uid_album_created`)
WHERE `uid` = %d AND `album` != '%s' AND `album` != '%s' $sql_extra
GROUP BY `album` ORDER BY `created` DESC",
intval($uid),
dbesc('Contact Photos'),
dbesc(t('Contact Photos'))
);
Cache::set($key, $albums, CACHE_DAY);
}
return $albums;
}

View file

@ -1,6 +1,15 @@
<?php
/**
* @file include/plaintext.php
*/
use \Friendica\ParseUrl;
require_once("include/Photo.php");
require_once("include/bbcode.php");
require_once("include/html2plain.php");
require_once("include/network.php");
/**
* @brief Fetches attachment data that were generated the old way
@ -181,20 +190,17 @@ function get_attached_data($body) {
// if nothing is found, it maybe having an image.
if (!isset($post["type"])) {
require_once("mod/parse_url.php");
require_once("include/Photo.php");
$URLSearchString = "^\[\]";
if (preg_match_all("(\[url=([$URLSearchString]*)\]\s*\[img\]([$URLSearchString]*)\[\/img\]\s*\[\/url\])ism", $body, $pictures, PREG_SET_ORDER)) {
if (count($pictures) == 1) {
// Checking, if the link goes to a picture
$data = parseurl_getsiteinfo_cached($pictures[0][1], true);
$data = ParseUrl::getSiteinfoCached($pictures[0][1], true);
// Workaround:
// Sometimes photo posts to the own album are not detected at the start.
// So we seem to cannot use the cache for these cases. That's strange.
if (($data["type"] != "photo") AND strstr($pictures[0][1], "/photos/"))
$data = parseurl_getsiteinfo($pictures[0][1], true);
$data = ParseUrl::getSiteinfo($pictures[0][1], true);
if ($data["type"] == "photo") {
$post["type"] = "photo";
@ -246,8 +252,7 @@ function get_attached_data($body) {
$post["text"] = trim($body);
}
} elseif (isset($post["url"]) AND ($post["type"] == "video")) {
require_once("mod/parse_url.php");
$data = parseurl_getsiteinfo_cached($post["url"], true);
$data = ParseUrl::getSiteinfoCached($post["url"], true);
if (isset($data["images"][0]))
$post["image"] = $data["images"][0]["src"];
@ -288,9 +293,6 @@ function shortenmsg($msg, $limit, $twitter = false) {
* @return string The converted message
*/
function plaintext($a, $b, $limit = 0, $includedlinks = false, $htmlmode = 2, $target_network = "") {
require_once("include/bbcode.php");
require_once("include/html2plain.php");
require_once("include/network.php");
// Remove the hash tags
$URLSearchString = "^\[\]";

View file

@ -15,7 +15,7 @@ use \Friendica\Core\PConfig;
require_once("boot.php");
function poller_run(&$argv, &$argc){
function poller_run($argv, $argc){
global $a, $db;
if(is_null($a)) {
@ -29,18 +29,27 @@ function poller_run(&$argv, &$argc){
unset($db_host, $db_user, $db_pass, $db_data);
};
// Quit when in maintenance
if (get_config('system', 'maintenance', true))
return;
$a->start_process();
$mypid = getmypid();
if ($a->max_processes_reached())
if (poller_max_connections_reached()) {
return;
}
if (poller_max_connections_reached())
if (App::maxload_reached()) {
return;
}
if (App::maxload_reached())
if(($argc <= 1) OR ($argv[1] != "no_cron")) {
poller_run_cron();
}
if ($a->max_processes_reached()) {
return;
}
// Checking the number of workers
if (poller_too_much_workers()) {
@ -48,91 +57,19 @@ function poller_run(&$argv, &$argc){
return;
}
if(($argc <= 1) OR ($argv[1] != "no_cron")) {
// Run the cron job that calls all other jobs
proc_run(PRIORITY_MEDIUM, "include/cron.php");
// Run the cronhooks job separately from cron for being able to use a different timing
proc_run(PRIORITY_MEDIUM, "include/cronhooks.php");
// Cleaning dead processes
poller_kill_stale_workers();
} else
// Sleep four seconds before checking for running processes again to avoid having too many workers
sleep(4);
// Checking number of workers
if (poller_too_much_workers())
return;
$cooldown = Config::get("system", "worker_cooldown", 0);
$starttime = time();
while ($r = q("SELECT * FROM `workerqueue` WHERE `executed` = '0000-00-00 00:00:00' ORDER BY `priority`, `created` LIMIT 1")) {
// Constantly check the number of parallel database processes
if ($a->max_processes_reached())
return;
// Constantly check the number of available database connections to let the frontend be accessible at any time
if (poller_max_connections_reached())
return;
while ($r = poller_worker_process()) {
// Count active workers and compare them with a maximum value that depends on the load
if (poller_too_much_workers())
if (poller_too_much_workers()) {
return;
q("UPDATE `workerqueue` SET `executed` = '%s', `pid` = %d WHERE `id` = %d AND `executed` = '0000-00-00 00:00:00'",
dbesc(datetime_convert()),
intval($mypid),
intval($r[0]["id"]));
// Assure that there are no tasks executed twice
$id = q("SELECT `pid`, `executed` FROM `workerqueue` WHERE `id` = %d", intval($r[0]["id"]));
if (!$id) {
logger("Queue item ".$r[0]["id"]." vanished - skip this execution", LOGGER_DEBUG);
continue;
} elseif ((strtotime($id[0]["executed"]) <= 0) OR ($id[0]["pid"] == 0)) {
logger("Entry for queue item ".$r[0]["id"]." wasn't stored - we better stop here", LOGGER_DEBUG);
return;
} elseif ($id[0]["pid"] != $mypid) {
logger("Queue item ".$r[0]["id"]." is to be executed by process ".$id[0]["pid"]." and not by me (".$mypid.") - skip this execution", LOGGER_DEBUG);
continue;
}
$argv = json_decode($r[0]["parameter"]);
$argc = count($argv);
// Check for existance and validity of the include file
$include = $argv[0];
if (!validate_include($include)) {
logger("Include file ".$argv[0]." is not valid!");
q("DELETE FROM `workerqueue` WHERE `id` = %d", intval($r[0]["id"]));
continue;
if (!poller_execute($r[0])) {
return;
}
require_once($include);
$funcname = str_replace(".php", "", basename($argv[0]))."_run";
if (function_exists($funcname)) {
logger("Process ".$mypid." - Prio ".$r[0]["priority"]." - ID ".$r[0]["id"].": ".$funcname." ".$r[0]["parameter"]);
$funcname($argv, $argc);
if ($cooldown > 0) {
logger("Process ".$mypid." - Prio ".$r[0]["priority"]." - ID ".$r[0]["id"].": ".$funcname." - in cooldown for ".$cooldown." seconds");
sleep($cooldown);
}
logger("Process ".$mypid." - Prio ".$r[0]["priority"]." - ID ".$r[0]["id"].": ".$funcname." - done");
q("DELETE FROM `workerqueue` WHERE `id` = %d", intval($r[0]["id"]));
} else
logger("Function ".$funcname." does not exist");
// Quit the poller once every hour
if (time() > ($starttime + 3600))
return;
@ -140,6 +77,108 @@ function poller_run(&$argv, &$argc){
}
/**
* @brief Execute a worker entry
*
* @param array $queue Workerqueue entry
*
* @return boolean "true" if further processing should be stopped
*/
function poller_execute($queue) {
$a = get_app();
$mypid = getmypid();
$cooldown = Config::get("system", "worker_cooldown", 0);
// Quit when in maintenance
if (get_config('system', 'maintenance', true)) {
return false;
}
// Constantly check the number of parallel database processes
if ($a->max_processes_reached()) {
return false;
}
// Constantly check the number of available database connections to let the frontend be accessible at any time
if (poller_max_connections_reached()) {
return false;
}
$upd = q("UPDATE `workerqueue` SET `executed` = '%s', `pid` = %d WHERE `id` = %d AND `pid` = 0",
dbesc(datetime_convert()),
intval($mypid),
intval($queue["id"]));
if (!$upd) {
logger("Couldn't update queue entry ".$queue["id"]." - skip this execution", LOGGER_DEBUG);
q("COMMIT");
return true;
}
// Assure that there are no tasks executed twice
$id = q("SELECT `pid`, `executed` FROM `workerqueue` WHERE `id` = %d", intval($queue["id"]));
if (!$id) {
logger("Queue item ".$queue["id"]." vanished - skip this execution", LOGGER_DEBUG);
q("COMMIT");
return true;
} elseif ((strtotime($id[0]["executed"]) <= 0) OR ($id[0]["pid"] == 0)) {
logger("Entry for queue item ".$queue["id"]." wasn't stored - skip this execution", LOGGER_DEBUG);
q("COMMIT");
return true;
} elseif ($id[0]["pid"] != $mypid) {
logger("Queue item ".$queue["id"]." is to be executed by process ".$id[0]["pid"]." and not by me (".$mypid.") - skip this execution", LOGGER_DEBUG);
q("COMMIT");
return true;
}
q("COMMIT");
$argv = json_decode($queue["parameter"]);
$argc = count($argv);
// Check for existance and validity of the include file
$include = $argv[0];
if (!validate_include($include)) {
logger("Include file ".$argv[0]." is not valid!");
q("DELETE FROM `workerqueue` WHERE `id` = %d", intval($queue["id"]));
return true;
}
require_once($include);
$funcname = str_replace(".php", "", basename($argv[0]))."_run";
if (function_exists($funcname)) {
logger("Process ".$mypid." - Prio ".$queue["priority"]." - ID ".$queue["id"].": ".$funcname." ".$queue["parameter"]);
// For better logging create a new process id for every worker call
// But preserve the old one for the worker
$old_process_id = $a->process_id;
$a->process_id = uniqid("wrk", true);
$funcname($argv, $argc);
$a->process_id = $old_process_id;
if ($cooldown > 0) {
logger("Process ".$mypid." - Prio ".$queue["priority"]." - ID ".$queue["id"].": ".$funcname." - in cooldown for ".$cooldown." seconds");
sleep($cooldown);
}
logger("Process ".$mypid." - Prio ".$queue["priority"]." - ID ".$queue["id"].": ".$funcname." - done");
q("DELETE FROM `workerqueue` WHERE `id` = %d", intval($queue["id"]));
} else {
logger("Function ".$funcname." does not exist");
}
return true;
}
/**
* @brief Checks if the number of database connections has reached a critical limit.
*
@ -151,9 +190,7 @@ function poller_max_connections_reached() {
$max = get_config("system", "max_connections");
// Fetch the percentage level where the poller will get active
$maxlevel = get_config("system", "max_connections_level");
if ($maxlevel == 0)
$maxlevel = 75;
$maxlevel = Config::get("system", "max_connections_level", 75);
if ($max == 0) {
// the maximum number of possible user connections can be a system variable
@ -269,13 +306,13 @@ function poller_kill_stale_workers() {
}
}
/**
* @brief Checks if the number of active workers exceeds the given limits
*
* @return bool Are there too much workers running?
*/
function poller_too_much_workers() {
$queues = get_config("system", "worker_queues");
if ($queues == 0)
$queues = 4;
$queues = Config::get("system", "worker_queues", 4);
$maxqueues = $queues;
@ -284,9 +321,7 @@ function poller_too_much_workers() {
// Decrease the number of workers at higher load
$load = current_load();
if($load) {
$maxsysload = intval(get_config('system','maxloadavg'));
if($maxsysload < 1)
$maxsysload = 50;
$maxsysload = intval(Config::get("system", "maxloadavg", 50));
$maxworkers = $queues;
@ -312,7 +347,28 @@ function poller_too_much_workers() {
}
}
logger("Current load: ".$load." - maximum: ".$maxsysload." - current queues: ".$active."/".$entries." - maximum: ".$queues."/".$maxqueues, LOGGER_DEBUG);
// Create a list of queue entries grouped by their priority
$running = array(PRIORITY_CRITICAL => 0,
PRIORITY_HIGH => 0,
PRIORITY_MEDIUM => 0,
PRIORITY_LOW => 0,
PRIORITY_NEGLIGIBLE => 0);
$r = q("SELECT COUNT(*) AS `running`, `priority` FROM `process` INNER JOIN `workerqueue` ON `workerqueue`.`pid` = `process`.`pid` GROUP BY `priority`");
if (dbm::is_result($r))
foreach ($r AS $process)
$running[$process["priority"]] = $process["running"];
$processlist = "";
$r = q("SELECT COUNT(*) AS `entries`, `priority` FROM `workerqueue` GROUP BY `priority`");
if (dbm::is_result($r))
foreach ($r as $entry) {
if ($processlist != "")
$processlist .= ", ";
$processlist .= $entry["priority"].":".$running[$entry["priority"]]."/".$entry["entries"];
}
logger("Load: ".$load."/".$maxsysload." - processes: ".$active."/".$entries." (".$processlist.") - maximum: ".$queues."/".$maxqueues, LOGGER_DEBUG);
// Are there fewer workers running as possible? Then fork a new one.
if (!get_config("system", "worker_dont_fork") AND ($queues > ($active + 1)) AND ($entries > 1)) {
@ -326,12 +382,193 @@ function poller_too_much_workers() {
return($active >= $queues);
}
/**
* @brief Returns the number of active poller processes
*
* @return integer Number of active poller processes
*/
function poller_active_workers() {
$workers = q("SELECT COUNT(*) AS `processes` FROM `process` WHERE `command` = 'poller.php'");
return($workers[0]["processes"]);
}
/**
* @brief Check if we should pass some slow processes
*
* When the active processes of the highest priority are using more than 2/3
* of all processes, we let pass slower processes.
*
* @param string $highest_priority Returns the currently highest priority
* @return bool We let pass a slower process than $highest_priority
*/
function poller_passing_slow(&$highest_priority) {
$highest_priority = 0;
$r = q("SELECT `priority`
FROM `process`
INNER JOIN `workerqueue` ON `workerqueue`.`pid` = `process`.`pid`");
// No active processes at all? Fine
if (!dbm::is_result($r))
return(false);
$priorities = array();
foreach ($r AS $line)
$priorities[] = $line["priority"];
// Should not happen
if (count($priorities) == 0)
return(false);
$highest_priority = min($priorities);
// The highest process is already the slowest one?
// Then we quit
if ($highest_priority == PRIORITY_NEGLIGIBLE)
return(false);
$high = 0;
foreach ($priorities AS $priority)
if ($priority == $highest_priority)
++$high;
logger("Highest priority: ".$highest_priority." Total processes: ".count($priorities)." Count high priority processes: ".$high, LOGGER_DEBUG);
$passing_slow = (($high/count($priorities)) > (2/3));
if ($passing_slow)
logger("Passing slower processes than priority ".$highest_priority, LOGGER_DEBUG);
return($passing_slow);
}
/**
* @brief Returns the next worker process
*
* @return string SQL statement
*/
function poller_worker_process() {
q("START TRANSACTION;");
// Check if we should pass some low priority process
$highest_priority = 0;
if (poller_passing_slow($highest_priority)) {
// Are there waiting processes with a higher priority than the currently highest?
$r = q("SELECT * FROM `workerqueue`
WHERE `executed` = '0000-00-00 00:00:00' AND `priority` < %d
ORDER BY `priority`, `created` LIMIT 1", dbesc($highest_priority));
if (dbm::is_result($r))
return $r;
// Give slower processes some processing time
$r = q("SELECT * FROM `workerqueue`
WHERE `executed` = '0000-00-00 00:00:00' AND `priority` > %d
ORDER BY `priority`, `created` LIMIT 1", dbesc($highest_priority));
}
// If there is no result (or we shouldn't pass lower processes) we check without priority limit
if (($highest_priority == 0) OR !dbm::is_result($r))
$r = q("SELECT * FROM `workerqueue` WHERE `executed` = '0000-00-00 00:00:00' ORDER BY `priority`, `created` LIMIT 1");
return $r;
}
/**
* @brief Call the front end worker
*/
function call_worker() {
if (!Config::get("system", "frontend_worker") OR !Config::get("system", "worker")) {
return;
}
$url = get_app()->get_baseurl()."/worker";
fetch_url($url, false, $redirects, 1);
}
/**
* @brief Call the front end worker if there aren't any active
*/
function call_worker_if_idle() {
if (!Config::get("system", "frontend_worker") OR !Config::get("system", "worker")) {
return;
}
// Do we have "proc_open"? Then we can fork the poller
if (function_exists("proc_open")) {
// When was the last time that we called the worker?
// Less than one minute? Then we quit
if ((time() - get_config("system", "worker_started")) < 60) {
return;
}
set_config("system", "worker_started", time());
// Do we have enough running workers? Then we quit here.
if (poller_too_much_workers()) {
// Cleaning dead processes
poller_kill_stale_workers();
get_app()->remove_inactive_processes();
return;
}
poller_run_cron();
logger('Call poller', LOGGER_DEBUG);
$args = array("php", "include/poller.php", "no_cron");
$a = get_app();
$a->proc_run($args);
return;
}
// We cannot execute background processes.
// We now run the processes from the frontend.
// This won't work with long running processes.
poller_run_cron();
clear_worker_processes();
$workers = q("SELECT COUNT(*) AS `processes` FROM `process` WHERE `command` = 'worker.php'");
if ($workers[0]["processes"] == 0) {
call_worker();
}
}
/**
* @brief Removes long running worker processes
*/
function clear_worker_processes() {
$timeout = Config::get("system", "frontend_worker_timeout", 10);
/// @todo We should clean up the corresponding workerqueue entries as well
q("DELETE FROM `process` WHERE `created` < '%s' AND `command` = 'worker.php'",
dbesc(datetime_convert('UTC','UTC',"now - ".$timeout." minutes")));
}
/**
* @brief Runs the cron processes
*/
function poller_run_cron() {
logger('Add cron entries', LOGGER_DEBUG);
// Check for spooled items
proc_run(PRIORITY_HIGH, "include/spool_post.php");
// Run the cron job that calls all other jobs
proc_run(PRIORITY_MEDIUM, "include/cron.php");
// Run the cronhooks job separately from cron for being able to use a different timing
proc_run(PRIORITY_MEDIUM, "include/cronhooks.php");
// Cleaning dead processes
poller_kill_stale_workers();
}
if (array_search(__file__,get_included_files())===0){
poller_run($_SERVER["argv"],$_SERVER["argc"]);

View file

@ -8,14 +8,18 @@
*/
function post_update() {
if (!post_update_1192())
if (!post_update_1192()) {
return;
if (!post_update_1194())
}
if (!post_update_1194()) {
return;
if (!post_update_1198())
}
if (!post_update_1198()) {
return;
}
if (!post_update_1206()) {
return;
}
}
/**
@ -174,13 +178,18 @@ function post_update_1198() {
}
// Update the thread table from the item table
q("UPDATE `thread` INNER JOIN `item` ON `item`.`id`=`thread`.`iid`
$r = q("UPDATE `thread` INNER JOIN `item` ON `item`.`id`=`thread`.`iid`
SET `thread`.`author-id` = `item`.`author-id`,
`thread`.`owner-id` = `item`.`owner-id`
WHERE `thread`.`author-id` = 0 AND `thread`.`owner-id` = 0 AND
(`thread`.`uid` IN (SELECT `uid` from `user`) OR `thread`.`uid` = 0)");
logger("Updated threads", LOGGER_DEBUG);
if (dbm::is_result($r)) {
set_config("system", "post_update_version", 1198);
logger("Done", LOGGER_DEBUG);
return true;
}
return false;
}
@ -215,4 +224,39 @@ function post_update_1198() {
logger("Updated items", LOGGER_DEBUG);
return false;
}
/**
* @brief update the "last-item" field in the "self" contact
*
* This field avoids cost intensive calls in the admin panel and in "nodeinfo"
*
* @return bool "true" when the job is done
*/
function post_update_1206() {
// Was the script completed?
if (get_config("system", "post_update_version") >= 1206)
return true;
logger("Start", LOGGER_DEBUG);
$r = q("SELECT `contact`.`id`, `contact`.`last-item`,
(SELECT MAX(`changed`) FROM `item` FORCE INDEX (`uid_wall_changed`) WHERE `wall` AND `uid` = `user`.`uid`) AS `lastitem_date`
FROM `user`
INNER JOIN `contact` ON `contact`.`uid` = `user`.`uid` AND `contact`.`self`");
if (!dbm::is_result($r)) {
return false;
}
foreach ($r AS $user) {
if (!empty($user["lastitem_date"]) AND ($user["lastitem_date"] > $user["last-item"])) {
q("UPDATE `contact` SET `last-item` = '%s' WHERE `id` = %d",
dbesc($user["lastitem_date"]),
intval($user["id"]));
}
}
set_config("system", "post_update_version", 1206);
logger("Done", LOGGER_DEBUG);
return true;
}
?>

View file

@ -15,22 +15,35 @@ function remove_queue_item($id) {
);
}
/**
* @brief Checks if the communication with a given contact had problems recently
*
* @param int $cid Contact id
*
* @return bool The communication with this contact has currently problems
*/
function was_recently_delayed($cid) {
$was_delayed = false;
// Are there queue entries that were recently added?
$r = q("SELECT `id` FROM `queue` WHERE `cid` = %d
and last > UTC_TIMESTAMP() - interval 15 minute limit 1",
AND `last` > UTC_TIMESTAMP() - interval 15 minute LIMIT 1",
intval($cid)
);
if(count($r))
return true;
$r = q("select `term-date` from contact where id = %d and `term-date` != '' and `term-date` != '0000-00-00 00:00:00' limit 1",
intval($cid)
);
if(count($r))
return true;
$was_delayed = dbm::is_result($r);
return false;
// We set "term-date" to a current date if the communication has problems.
// If the communication works again we reset this value.
if ($was_delayed) {
$r = q("SELECT `term-date` FROM `contact` WHERE `id` = %d AND `term-date` <= '1000-01-01' LIMIT 1",
intval($cid)
);
$was_delayed = !dbm::is_result($r);
}
return $was_delayed;
}

View file

@ -0,0 +1,52 @@
<?php
/**
* @file include/remove_contact.php
* @brief Removes orphaned data from deleted contacts
*/
require_once("boot.php");
function remove_contact_run($argv, $argc) {
global $a, $db;
if (is_null($a)) {
$a = new App;
}
if (is_null($db)) {
@include(".htconfig.php");
require_once("include/dba.php");
$db = new dba($db_host, $db_user, $db_pass, $db_data);
unset($db_host, $db_user, $db_pass, $db_data);
}
load_config('config');
load_config('system');
if ($argc != 2) {
return;
}
$id = intval($argv[1]);
// Only delete if the contact doesn't exist (anymore)
$r = q("SELECT `id` FROM `contact` WHERE `id` = %d", intval($id));
if (dbm::is_result($r)) {
return;
}
q("DELETE FROM `item` WHERE `contact-id` = %d", intval($id));
q("DELETE FROM `photo` WHERE `contact-id` = %d", intval($id));
q("DELETE FROM `mail` WHERE `contact-id` = %d", intval($id));
q("DELETE FROM `event` WHERE `cid` = %d", intval($id));
q("DELETE FROM `queue` WHERE `cid` = %d", intval($id));
}
if (array_search(__file__, get_included_files()) === 0) {
remove_contact_run($_SERVER["argv"], $_SERVER["argc"]);
killme();
}
?>

View file

@ -31,8 +31,7 @@ function get_salmon_key($uri,$keyhash) {
$ret[$x] = substr($ret[$x],strpos($ret[$x],',')+1);
else
$ret[$x] = substr($ret[$x],5);
}
else
} elseif (normalise_link($ret[$x]) == 'http://')
$ret[$x] = fetch_url($ret[$x]);
}
}

View file

@ -79,11 +79,9 @@ function authenticate_success($user_record, $login_initial = false, $interactive
header('X-Account-Management-Status: active; name="' . $a->user['username'] . '"; id="' . $a->user['nickname'] .'"');
if($login_initial || $login_refresh) {
$l = get_browser_language();
q("UPDATE `user` SET `login_date` = '%s', `language` = '%s' WHERE `uid` = %d",
q("UPDATE `user` SET `login_date` = '%s' WHERE `uid` = %d",
dbesc(datetime_convert()),
dbesc($l),
intval($_SESSION['uid'])
);

View file

@ -1,76 +1,111 @@
<?php
// Session management functions. These provide database storage of PHP
// session info.
require_once('include/cache.php');
$session_exists = 0;
$session_expire = 180000;
if(! function_exists('ref_session_open')) {
function ref_session_open ($s,$n) {
function ref_session_open($s, $n) {
return true;
}}
}
if(! function_exists('ref_session_read')) {
function ref_session_read ($id) {
function ref_session_read($id) {
global $session_exists;
if(x($id))
$r = q("SELECT `data` FROM `session` WHERE `sid`= '%s'", dbesc($id));
if(count($r)) {
if (!x($id)) {
return '';
}
$memcache = cache::memcache();
if (is_object($memcache)) {
$data = $memcache->get(get_app()->get_hostname().":session:".$id);
if (!is_bool($data)) {
return $data;
}
logger("no data for session $id", LOGGER_TRACE);
return '';
}
$r = q("SELECT `data` FROM `session` WHERE `sid`= '%s'", dbesc($id));
if (dbm::is_result($r)) {
$session_exists = true;
return $r[0]['data'];
} else {
logger("no data for session $id", LOGGER_TRACE);
}
return '';
}}
if(! function_exists('ref_session_write')) {
function ref_session_write ($id,$data) {
return '';
}
/**
* @brief Standard PHP session write callback
*
* This callback updates the DB-stored session data and/or the expiration depending
* on the case. Uses the $session_expire global for existing session, 5 minutes
* for newly created session.
*
* @global bool $session_exists Whether a session with the given id already exists
* @global int $session_expire Session expiration delay in seconds
* @param string $id Session ID with format: [a-z0-9]{26}
* @param string $data Serialized session data
* @return boolean Returns false if parameters are missing, true otherwise
*/
function ref_session_write($id, $data) {
global $session_exists, $session_expire;
if(! $id || ! $data) {
if (!$id || !$data) {
return false;
}
$expire = time() + $session_expire;
$default_expire = time() + 300;
if($session_exists) {
$r = q("UPDATE `session`
SET `data` = '%s'
WHERE `data` != '%s' AND `sid` = '%s'",
dbesc($data), dbesc($data), dbesc($id));
$memcache = cache::memcache();
if (is_object($memcache)) {
$memcache->set(get_app()->get_hostname().":session:".$id, $data, MEMCACHE_COMPRESSED, $expire);
return true;
}
if ($session_exists) {
$r = q("UPDATE `session`
SET `expire` = '%s'
WHERE `expire` != '%s' AND `sid` = '%s'",
dbesc($expire), dbesc($expire), dbesc($id));
} else
SET `data` = '%s', `expire` = '%s'
WHERE `sid` = '%s'
AND (`data` != '%s' OR `expire` != '%s')",
dbesc($data), dbesc($expire), dbesc($id), dbesc($data), dbesc($expire));
} else {
$r = q("INSERT INTO `session`
SET `sid` = '%s', `expire` = '%s', `data` = '%s'",
dbesc($id), dbesc($default_expire), dbesc($data));
}
return true;
}}
}
if(! function_exists('ref_session_close')) {
function ref_session_close() {
return true;
}}
}
function ref_session_destroy($id) {
$memcache = cache::memcache();
if (is_object($memcache)) {
$memcache->delete(get_app()->get_hostname().":session:".$id);
return true;
}
if(! function_exists('ref_session_destroy')) {
function ref_session_destroy ($id) {
q("DELETE FROM `session` WHERE `sid` = '%s'", dbesc($id));
return true;
}}
if(! function_exists('ref_session_gc')) {
return true;
}
function ref_session_gc($expire) {
q("DELETE FROM `session` WHERE `expire` < %d", dbesc(time()));
return true;
}}
}
$gc_probability = 50;
@ -78,7 +113,8 @@ ini_set('session.gc_probability', $gc_probability);
ini_set('session.use_only_cookies', 1);
ini_set('session.cookie_httponly', 1);
if (!get_config('system', 'disable_database_session'))
if (!get_config('system', 'disable_database_session')) {
session_set_save_handler('ref_session_open', 'ref_session_close',
'ref_session_read', 'ref_session_write',
'ref_session_destroy', 'ref_session_gc');
}

View file

@ -52,7 +52,7 @@ function poco_load($cid,$uid = 0,$zcid = 0,$url = null) {
if(! $url)
return;
$url = $url . (($uid) ? '/@me/@all?fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,generation' : '?fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,generation') ;
$url = $url . (($uid) ? '/@me/@all?fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,contactType,generation' : '?fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,contactType,generation') ;
logger('poco_load: ' . $url, LOGGER_DEBUG);
@ -86,6 +86,7 @@ function poco_load($cid,$uid = 0,$zcid = 0,$url = null) {
$about = '';
$keywords = '';
$gender = '';
$contact_type = -1;
$generation = 0;
$name = $entry->displayName;
@ -133,6 +134,9 @@ function poco_load($cid,$uid = 0,$zcid = 0,$url = null) {
foreach($entry->tags as $tag)
$keywords = implode(", ", $tag);
if(isset($entry->contactType) AND ($entry->contactType >= 0))
$contact_type = $entry->contactType;
// If you query a Friendica server for its profiles, the network has to be Friendica
/// TODO It could also be a Redmatrix server
//if ($uid == 0)
@ -140,6 +144,9 @@ function poco_load($cid,$uid = 0,$zcid = 0,$url = null) {
poco_check($profile_url, $name, $network, $profile_photo, $about, $location, $gender, $keywords, $connect_url, $updated, $generation, $cid, $uid, $zcid);
$gcontact = array("url" => $profile_url, "contact-type" => $contact_type, "generation" => $generation);
update_gcontact($gcontact);
// Update the Friendica contacts. Diaspora is doing it via a message. (See include/diaspora.php)
// Deactivated because we now update Friendica contacts in dfrn.php
//if (($location != "") OR ($about != "") OR ($keywords != "") OR ($gender != ""))
@ -384,6 +391,15 @@ function poco_detect_server($profile) {
}
}
// Mastodon
if ($server_url == "") {
$red = preg_replace("=(https?://)(.*)/users/(.*)=ism", "$1$2", $profile);
if ($red != $profile) {
$server_url = $red;
$network = NETWORK_OSTATUS;
}
}
return $server_url;
}
@ -748,6 +764,13 @@ function poco_check_server($server_url, $network = "", $force = false) {
$versionparts = explode("-", $version);
$version = $versionparts[0];
}
if(stristr($line,'Server: Mastodon')) {
$platform = "Mastodon";
$network = NETWORK_OSTATUS;
// Mastodon doesn't reveal version numbers
$version = "";
}
}
}
}
@ -1068,8 +1091,16 @@ function all_friends($uid,$cid,$start = 0, $limit = 80) {
function suggestion_query($uid, $start = 0, $limit = 80) {
if(! $uid)
if (!$uid) {
return array();
}
// Uncommented because the result of the queries are to big to store it in the cache.
// We need to decide if we want to change the db column type or if we want to delete it.
// $list = Cache::get("suggestion_query:".$uid.":".$start.":".$limit);
// if (!is_null($list)) {
// return $list;
// }
$network = array(NETWORK_DFRN);
@ -1080,9 +1111,10 @@ function suggestion_query($uid, $start = 0, $limit = 80) {
$network[] = NETWORK_OSTATUS;
$sql_network = implode("', '", $network);
//$sql_network = "'".$sql_network."', ''";
$sql_network = "'".$sql_network."'";
/// @todo This query is really slow
// By now we cache the data for five minutes
$r = q("SELECT count(glink.gcid) as `total`, gcontact.* from gcontact
INNER JOIN `glink` ON `glink`.`gcid` = `gcontact`.`id`
where uid = %d and not gcontact.nurl in ( select nurl from contact where uid = %d )
@ -1101,8 +1133,13 @@ function suggestion_query($uid, $start = 0, $limit = 80) {
intval($limit)
);
if(count($r) && count($r) >= ($limit -1))
if (count($r) && count($r) >= ($limit -1)) {
// Uncommented because the result of the queries are to big to store it in the cache.
// We need to decide if we want to change the db column type or if we want to delete it.
// Cache::set("suggestion_query:".$uid.":".$start.":".$limit, $r, CACHE_FIVE_MINUTES);
return $r;
}
$r2 = q("SELECT gcontact.* FROM gcontact
INNER JOIN `glink` ON `glink`.`gcid` = `gcontact`.`id`
@ -1131,6 +1168,9 @@ function suggestion_query($uid, $start = 0, $limit = 80) {
while (sizeof($list) > ($limit))
array_pop($list);
// Uncommented because the result of the queries are to big to store it in the cache.
// We need to decide if we want to change the db column type or if we want to delete it.
// Cache::set("suggestion_query:".$uid.":".$start.":".$limit, $list, CACHE_FIVE_MINUTES);
return $list;
}
@ -1236,7 +1276,7 @@ function poco_discover($complete = false) {
}
// Fetch all users from the other server
$url = $server["poco"]."/?fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,generation";
$url = $server["poco"]."/?fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,contactType,generation";
logger("Fetch all users from the server ".$server["nurl"], LOGGER_DEBUG);
@ -1255,7 +1295,7 @@ function poco_discover($complete = false) {
$updatedSince = date("Y-m-d H:i:s", time() - $timeframe * 86400);
// Fetch all global contacts from the other server (Not working with Redmatrix and Friendica versions before 3.3)
$url = $server["poco"]."/@global?updatedSince=".$updatedSince."&fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,generation";
$url = $server["poco"]."/@global?updatedSince=".$updatedSince."&fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,contactType,generation";
$success = false;
@ -1303,7 +1343,7 @@ function poco_discover_server_users($data, $server) {
logger("Fetch contacts for the user ".$username." from the server ".$server["nurl"], LOGGER_DEBUG);
// Fetch all contacts from a given user from the other server
$url = $server["poco"]."/".$username."/?fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,generation";
$url = $server["poco"]."/".$username."/?fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,contactType,generation";
$retdata = z_fetch_url($url);
if ($retdata["success"])
@ -1330,6 +1370,7 @@ function poco_discover_server($data, $default_generation = 0) {
$about = '';
$keywords = '';
$gender = '';
$contact_type = -1;
$generation = $default_generation;
$name = $entry->displayName;
@ -1374,6 +1415,9 @@ function poco_discover_server($data, $default_generation = 0) {
if(isset($entry->generation) AND ($entry->generation > 0))
$generation = ++$entry->generation;
if(isset($entry->contactType) AND ($entry->contactType >= 0))
$contact_type = $entry->contactType;
if(isset($entry->tags))
foreach($entry->tags as $tag)
$keywords = implode(", ", $tag);
@ -1383,6 +1427,10 @@ function poco_discover_server($data, $default_generation = 0) {
logger("Store profile ".$profile_url, LOGGER_DEBUG);
poco_check($profile_url, $name, $network, $profile_photo, $about, $location, $gender, $keywords, $connect_url, $updated, $generation, 0, 0, 0);
$gcontact = array("url" => $profile_url, "contact-type" => $contact_type, "generation" => $generation);
update_gcontact($gcontact);
logger("Done for profile ".$profile_url, LOGGER_DEBUG);
}
}
@ -1534,7 +1582,7 @@ function update_gcontact($contact) {
return false;
$r = q("SELECT `name`, `nick`, `photo`, `location`, `about`, `addr`, `generation`, `birthday`, `gender`, `keywords`,
`hide`, `nsfw`, `network`, `alias`, `notify`, `server_url`, `connect`, `updated`, `url`
`contact-type`, `hide`, `nsfw`, `network`, `alias`, `notify`, `server_url`, `connect`, `updated`, `url`
FROM `gcontact` WHERE `id` = %d LIMIT 1",
intval($gcontact_id));
@ -1614,20 +1662,20 @@ function update_gcontact($contact) {
}
if ($update) {
logger("Update gcontact for ".$contact["url"]." Callstack: ".App::callstack(), LOGGER_DEBUG);
logger("Update gcontact for ".$contact["url"], LOGGER_DEBUG);
q("UPDATE `gcontact` SET `photo` = '%s', `name` = '%s', `nick` = '%s', `addr` = '%s', `network` = '%s',
`birthday` = '%s', `gender` = '%s', `keywords` = '%s', `hide` = %d, `nsfw` = %d,
`alias` = '%s', `notify` = '%s', `url` = '%s',
`contact-type` = %d, `alias` = '%s', `notify` = '%s', `url` = '%s',
`location` = '%s', `about` = '%s', `generation` = %d, `updated` = '%s',
`server_url` = '%s', `connect` = '%s'
WHERE `nurl` = '%s' AND (`generation` = 0 OR `generation` >= %d)",
dbesc($contact["photo"]), dbesc($contact["name"]), dbesc($contact["nick"]),
dbesc($contact["addr"]), dbesc($contact["network"]), dbesc($contact["birthday"]),
dbesc($contact["gender"]), dbesc($contact["keywords"]), intval($contact["hide"]),
intval($contact["nsfw"]), dbesc($contact["alias"]), dbesc($contact["notify"]),
dbesc($contact["url"]), dbesc($contact["location"]), dbesc($contact["about"]),
intval($contact["generation"]), dbesc($contact["updated"]),
intval($contact["nsfw"]), intval($contact["contact-type"]), dbesc($contact["alias"]),
dbesc($contact["notify"]), dbesc($contact["url"]), dbesc($contact["location"]),
dbesc($contact["about"]), intval($contact["generation"]), dbesc($contact["updated"]),
dbesc($contact["server_url"]), dbesc($contact["connect"]),
dbesc(normalise_link($contact["url"])), intval($contact["generation"]));
@ -1644,13 +1692,14 @@ function update_gcontact($contact) {
q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `addr` = '%s',
`network` = '%s', `bd` = '%s', `gender` = '%s',
`keywords` = '%s', `alias` = '%s', `url` = '%s',
`location` = '%s', `about` = '%s'
`keywords` = '%s', `alias` = '%s', `contact-type` = %d,
`url` = '%s', `location` = '%s', `about` = '%s'
WHERE `id` = %d",
dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["addr"]),
dbesc($contact["network"]), dbesc($contact["birthday"]), dbesc($contact["gender"]),
dbesc($contact["keywords"]), dbesc($contact["alias"]), dbesc($contact["url"]),
dbesc($contact["location"]), dbesc($contact["about"]), intval($r[0]["id"]));
dbesc($contact["keywords"]), dbesc($contact["alias"]), intval($contact["contact-type"]),
dbesc($contact["url"]), dbesc($contact["location"]), dbesc($contact["about"]),
intval($r[0]["id"]));
}
}

49
include/spool_post.php Normal file
View file

@ -0,0 +1,49 @@
<?php
/**
* @file include/spool_post.php
* @brief Posts items that wer spooled because they couldn't be posted.
*/
require_once("boot.php");
require_once("include/items.php");
function spool_post_run($argv, $argc) {
global $a, $db;
if (is_null($a)) {
$a = new App;
}
if (is_null($db)) {
@include(".htconfig.php");
require_once("include/dba.php");
$db = new dba($db_host, $db_user, $db_pass, $db_data);
unset($db_host, $db_user, $db_pass, $db_data);
}
load_config('config');
load_config('system');
$path = get_spoolpath();
if (is_writable($path)){
if ($dh = opendir($path)) {
while (($file = readdir($dh)) !== false) {
$fullfile = $path."/".$file;
if (filetype($fullfile) != "file") {
continue;
}
$arr = json_decode(file_get_contents($fullfile), true);
$result = item_store($arr);
logger("Spool file ".$file." stored: ".$result, LOGGER_DEBUG);
unlink($fullfile);
}
closedir($dh);
}
}
}
if (array_search(__file__, get_included_files()) === 0) {
spool_post_run($_SERVER["argv"], $_SERVER["argc"]);
killme();
}
?>

View file

@ -12,7 +12,7 @@ if(! function_exists('replace_macros')) {
* This is our template processor
*
* @param string|FriendicaSmarty $s the string requiring macro substitution,
* or an instance of FriendicaSmarty
* or an instance of FriendicaSmarty
* @param array $r key value pairs (search => replace)
* @return string substituted string
*/
@ -581,14 +581,14 @@ function get_intltext_template($s) {
if(! isset($lang))
$lang = 'en';
if(file_exists("view/$lang$engine/$s")) {
if(file_exists("view/lang/$lang$engine/$s")) {
$stamp1 = microtime(true);
$content = file_get_contents("view/$lang$engine/$s");
$content = file_get_contents("view/lang/$lang$engine/$s");
$a->save_timestamp($stamp1, "file");
return $content;
} elseif(file_exists("view/en$engine/$s")) {
} elseif(file_exists("view/lang/en$engine/$s")) {
$stamp1 = microtime(true);
$content = file_get_contents("view/en$engine/$s");
$content = file_get_contents("view/lang/en$engine/$s");
$a->save_timestamp($stamp1, "file");
return $content;
} else {
@ -678,11 +678,13 @@ function attribute_contains($attr,$s) {
return false;
}}
if(! function_exists('logger')) {
if (! function_exists('logger')) {
/* setup int->string log level map */
$LOGGER_LEVELS = array();
/**
* @brief Logs the given message at the given log level
*
* log levels:
* LOGGER_NORMAL (default)
* LOGGER_TRACE
@ -692,51 +694,63 @@ $LOGGER_LEVELS = array();
*
* @global App $a
* @global dba $db
* @global array $LOGGER_LEVELS
* @param string $msg
* @param int $level
*/
function logger($msg,$level = 0) {
// turn off logger in install mode
function logger($msg, $level = 0) {
global $a;
global $db;
global $LOGGER_LEVELS;
if(($a->module == 'install') || (! ($db && $db->connected))) return;
if (count($LOGGER_LEVELS)==0){
foreach (get_defined_constants() as $k=>$v){
if (substr($k,0,7)=="LOGGER_")
$LOGGER_LEVELS[$v] = substr($k,7,7);
}
// turn off logger in install mode
if (
$a->module == 'install'
|| ! ($db && $db->connected)
) {
return;
}
$debugging = get_config('system','debugging');
$loglevel = intval(get_config('system','loglevel'));
$logfile = get_config('system','logfile');
$loglevel = intval(get_config('system','loglevel'));
if((! $debugging) || (! $logfile) || ($level > $loglevel))
if (
! $debugging
|| ! $logfile
|| $level > $loglevel
) {
return;
}
if (count($LOGGER_LEVELS) == 0) {
foreach (get_defined_constants() as $k => $v) {
if (substr($k, 0, 7) == "LOGGER_") {
$LOGGER_LEVELS[$v] = substr($k, 7, 7);
}
}
}
$process_id = session_id();
if ($process_id == "")
if ($process_id == '') {
$process_id = get_app()->process_id;
}
$callers = debug_backtrace();
$logline = sprintf("%s@%s\t[%s]:%s:%s:%s\t%s\n",
datetime_convert(),
$process_id,
$LOGGER_LEVELS[$level],
basename($callers[0]['file']),
$callers[0]['line'],
$callers[1]['function'],
$msg
);
$logline = sprintf("%s@%s\t[%s]:%s:%s:%s\t%s\n",
datetime_convert(),
$process_id,
$LOGGER_LEVELS[$level],
basename($callers[0]['file']),
$callers[0]['line'],
$callers[1]['function'],
$msg
);
$stamp1 = microtime(true);
@file_put_contents($logfile, $logline, FILE_APPEND);
$a->save_timestamp($stamp1, "file");
return;
}}
@ -755,71 +769,75 @@ function activity_match($haystack,$needle) {
}}
if(! function_exists('get_tags')) {
/**
* Pull out all #hashtags and @person tags from $s;
* @brief Pull out all #hashtags and @person tags from $string.
*
* We also get @person@domain.com - which would make
* the regex quite complicated as tags can also
* end a sentence. So we'll run through our results
* and strip the period from any tags which end with one.
* Returns array of tags found, or empty array.
*
* @param string $s
* @return array
* @param string $string Post content
* @return array List of tag and person names
*/
function get_tags($s) {
function get_tags($string) {
$ret = array();
// Convert hashtag links to hashtags
$s = preg_replace("/#\[url\=([^\[\]]*)\](.*?)\[\/url\]/ism", "#$2", $s);
$string = preg_replace('/#\[url\=([^\[\]]*)\](.*?)\[\/url\]/ism', '#$2', $string);
// ignore anything in a code block
$s = preg_replace('/\[code\](.*?)\[\/code\]/sm','',$s);
$string = preg_replace('/\[code\](.*?)\[\/code\]/sm', '', $string);
// Force line feeds at bbtags
$s = str_replace(array("[", "]"), array("\n[", "]\n"), $s);
$string = str_replace(array('[', ']'), array("\n[", "]\n"), $string);
// ignore anything in a bbtag
$s = preg_replace('/\[(.*?)\]/sm','',$s);
$string = preg_replace('/\[(.*?)\]/sm', '', $string);
// 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.
if(preg_match_all('/(@[^ \x0D\x0A,:?]+ [^ \x0D\x0A@,:?]+)([ \x0D\x0A@,:?]|$)/',$s,$match)) {
foreach($match[1] as $mtch) {
if(strstr($mtch,"]")) {
if (preg_match_all('/(@[^ \x0D\x0A,:?]+ [^ \x0D\x0A@,:?]+)([ \x0D\x0A@,:?]|$)/', $string, $matches)) {
foreach ($matches[1] as $match) {
if (strstr($match, ']')) {
// we might be inside a bbcode color tag - leave it alone
continue;
}
if(substr($mtch,-1,1) === '.')
$ret[] = substr($mtch,0,-1);
else
$ret[] = $mtch;
if (substr($match, -1, 1) === '.') {
$ret[] = substr($match, 0, -1);
} else {
$ret[] = $match;
}
}
}
// Otherwise pull out single word tags. These can be @nickname, @first_last
// and #hash tags.
if(preg_match_all('/([!#@][^ \x0D\x0A,;:?]+)([ \x0D\x0A,;:?]|$)/',$s,$match)) {
foreach($match[1] as $mtch) {
if(strstr($mtch,"]")) {
if (preg_match_all('/([!#@][^\^ \x0D\x0A,;:?]+)([ \x0D\x0A,;:?]|$)/', $string, $matches)) {
foreach($matches[1] as $match) {
if (strstr($match, ']')) {
// we might be inside a bbcode color tag - leave it alone
continue;
}
if(substr($mtch,-1,1) === '.')
$mtch = substr($mtch,0,-1);
if (substr($match, -1, 1) === '.') {
$match = substr($match,0,-1);
}
// ignore strictly numeric tags like #1
if((strpos($mtch,'#') === 0) && ctype_digit(substr($mtch,1)))
if ((strpos($match, '#') === 0) && ctype_digit(substr($match, 1))) {
continue;
}
// try not to catch url fragments
if(strpos($s,$mtch) && preg_match('/[a-zA-z0-9\/]/',substr($s,strpos($s,$mtch)-1,1)))
if (strpos($string, $match) && preg_match('/[a-zA-z0-9\/]/', substr($string, strpos($string, $match) - 1, 1))) {
continue;
$ret[] = $mtch;
}
$ret[] = $match;
}
}
return $ret;
}}
}
//
@ -856,8 +874,8 @@ function contact_block() {
if((! is_array($a->profile)) || ($a->profile['hide-friends']))
return $o;
$r = q("SELECT COUNT(*) AS `total` FROM `contact`
WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0
AND `hidden` = 0 AND `archive` = 0
WHERE `uid` = %d AND NOT `self` AND NOT `blocked`
AND NOT `hidden` AND NOT `archive`
AND `network` IN ('%s', '%s', '%s')",
intval($a->profile['uid']),
dbesc(NETWORK_DFRN),
@ -874,7 +892,7 @@ function contact_block() {
} else {
// Splitting the query in two parts makes it much faster
$r = q("SELECT `id` FROM `contact`
WHERE `uid` = %d AND NOT `self` AND NOT `blocked` AND NOT `pending`
WHERE `uid` = %d AND NOT `self` AND NOT `blocked`
AND NOT `hidden` AND NOT `archive`
AND `network` IN ('%s', '%s', '%s') ORDER BY RAND() LIMIT %d",
intval($a->profile['uid']),
@ -988,7 +1006,7 @@ function search($s,$id='search-box',$url='search',$save = false, $aside = true)
$a = get_app();
$values = array(
'$s' => $s,
'$s' => htmlspecialchars($s),
'$id' => $id,
'$action_url' => $url,
'$search_label' => t('Search'),
@ -1152,33 +1170,29 @@ function link_compare($a,$b) {
return false;
}}
if(! function_exists('redir_private_images')) {
/**
* Find any non-embedded images in private items and add redir links to them
* @brief Find any non-embedded images in private items and add redir links to them
*
* @param App $a
* @param array $item
* @param array &$item The field array of an item row
*/
function redir_private_images($a, &$item) {
function redir_private_images($a, &$item)
{
$matches = false;
$cnt = preg_match_all('|\[img\](http[^\[]*?/photo/[a-fA-F0-9]+?(-[0-9]\.[\w]+?)?)\[\/img\]|', $item['body'], $matches, PREG_SET_ORDER);
if($cnt) {
//logger("redir_private_images: matches = " . print_r($matches, true));
foreach($matches as $mtch) {
if(strpos($mtch[1], '/redir') !== false)
if ($cnt) {
foreach ($matches as $mtch) {
if (strpos($mtch[1], '/redir') !== false) {
continue;
}
if((local_user() == $item['uid']) && ($item['private'] != 0) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN)) {
//logger("redir_private_images: redir");
$img_url = 'redir?f=1&quiet=1&url=' . $mtch[1] . '&conurl=' . $item['author-link'];
$item['body'] = str_replace($mtch[0], "[img]".$img_url."[/img]", $item['body']);
if ((local_user() == $item['uid']) && ($item['private'] != 0) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN)) {
$img_url = 'redir?f=1&quiet=1&url=' . urlencode($mtch[1]) . '&conurl=' . urlencode($item['author-link']);
$item['body'] = str_replace($mtch[0], '[img]' . $img_url . '[/img]', $item['body']);
}
}
}
}}
}
function put_item_in_cache(&$item, $update = false) {

View file

@ -18,42 +18,70 @@ function add_thread($itemid, $onlyshadow = false) {
.implode("', '", array_values($item))
."')");
logger("add_thread: Add thread for item ".$itemid." - ".print_r($result, true), LOGGER_DEBUG);
logger("Add thread for item ".$itemid." - ".print_r($result, true), LOGGER_DEBUG);
}
}
/**
* @brief Add a shadow entry for a given item id that is a thread starter
*
* We store every public item entry additionally with the user id "0".
* This is used for the community page and for the search.
* It is planned that in the future we will store public item entries only once.
*
* @param integer $itemid Item ID that should be added
*/
function add_shadow_thread($itemid) {
$items = q("SELECT `uid`, `wall`, `private`, `moderated`, `visible`, `contact-id`, `deleted`, `network`
FROM `item` WHERE `id` = %d AND (`parent` = %d OR `parent` = 0) LIMIT 1", intval($itemid), intval($itemid));
if (!dbm::is_result($items)) {
return;
}
$item = $items[0];
// is it already a copy?
if (($itemid == 0) OR ($item['uid'] == 0))
if (($itemid == 0) OR ($item['uid'] == 0)) {
return;
}
// Is it a visible public post?
if (!$item["visible"] OR $item["deleted"] OR $item["moderated"] OR $item["private"])
if (!$item["visible"] OR $item["deleted"] OR $item["moderated"] OR $item["private"]) {
return;
}
// is it an entry from a connector? Only add an entry for natively connected networks
if (!in_array($item["network"], array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS, "")))
if (!in_array($item["network"], array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS, ""))) {
return;
}
// Only do these checks if the post isn't a wall post
if (!$item["wall"]) {
// Check, if hide-friends is activated - then don't do a shadow entry
$r = q("SELECT `hide-friends` FROM `profile` WHERE `is-default` AND `uid` = %d AND NOT `hide-friends`",
$item['uid']);
if (!count($r))
if (!dbm::is_result($r)) {
return;
}
// Check if the contact is hidden or blocked
$r = q("SELECT `id` FROM `contact` WHERE NOT `hidden` AND NOT `blocked` AND `id` = %d",
$item['contact-id']);
if (!count($r))
if (!dbm::is_result($r)) {
return;
}
}
// Only add a shadow, if the profile isn't hidden
$r = q("SELECT `uid` FROM `user` where `uid` = %d AND NOT `hidewall`", $item['uid']);
if (!count($r))
if (!dbm::is_result($r)) {
return;
}
$item = q("SELECT * FROM `item` WHERE `id` = %d",
intval($itemid));
$item = q("SELECT * FROM `item` WHERE `id` = %d", intval($itemid));
if (count($item) AND ($item[0]["allow_cid"] == '') AND ($item[0]["allow_gid"] == '') AND
($item[0]["deny_cid"] == '') AND ($item[0]["deny_gid"] == '')) {
@ -61,7 +89,7 @@ function add_thread($itemid, $onlyshadow = false) {
$r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = 0 LIMIT 1",
dbesc($item['uri']));
if (!$r) {
if (!dbm::is_result($r)) {
// Preparing public shadow (removing user specific data)
require_once("include/items.php");
require_once("include/Contact.php");
@ -69,15 +97,44 @@ function add_thread($itemid, $onlyshadow = false) {
unset($item[0]['id']);
$item[0]['uid'] = 0;
$item[0]['origin'] = 0;
$item[0]['wall'] = 0;
$item[0]['contact-id'] = get_contact($item[0]['author-link'], 0);
if (in_array($item[0]['type'], array("net-comment", "wall-comment"))) {
$item[0]['type'] = 'remote-comment';
} elseif ($item[0]['type'] == 'wall') {
$item[0]['type'] = 'remote';
}
$public_shadow = item_store($item[0], false, false, true);
logger("add_thread: Stored public shadow for post ".$itemid." under id ".$public_shadow, LOGGER_DEBUG);
logger("Stored public shadow for thread ".$itemid." under id ".$public_shadow, LOGGER_DEBUG);
}
}
}
function add_shadow_entry($item) {
/**
* @brief Add a shadow entry for a given item id that is a comment
*
* This function does the same like the function above - but for comments
*
* @param integer $itemid Item ID that should be added
*/
function add_shadow_entry($itemid) {
$items = q("SELECT * FROM `item` WHERE `id` = %d", intval($itemid));
if (!dbm::is_result($items)) {
return;
}
$item = $items[0];
// Is it a toplevel post?
if ($item['id'] == $item['parent']) {
add_shadow_thread($itemid);
return;
}
// Is this a shadow entry?
if ($item['uid'] == 0)
@ -99,7 +156,16 @@ function add_shadow_entry($item) {
unset($item['id']);
$item['uid'] = 0;
$item['origin'] = 0;
$item['wall'] = 0;
$item['contact-id'] = get_contact($item['author-link'], 0);
if (in_array($item['type'], array("net-comment", "wall-comment"))) {
$item['type'] = 'remote-comment';
} elseif ($item['type'] == 'wall') {
$item['type'] = 'remote';
}
$public_shadow = item_store($item, false, false, true);
logger("Stored public shadow for comment ".$item['uri']." under id ".$public_shadow, LOGGER_DEBUG);
@ -193,8 +259,10 @@ function update_threads() {
logger("update_threads: fetched messages: ".count($messages));
while ($message = $db->qfetch())
while ($message = $db->qfetch()) {
add_thread($message["id"]);
add_shadow_thread($message["id"]);
}
$db->qclose();
}
@ -227,7 +295,7 @@ function update_shadow_copy() {
logger("fetched messages: ".count($messages));
while ($message = $db->qfetch())
add_thread($message["iid"], true);
add_shadow_thread($message["iid"]);
$db->qclose();
}

View file

@ -378,6 +378,29 @@ function create_user($arr) {
}
/**
* @brief send registration confiŕmation with the intormation that reg is pending
*
* @param string $email
* @param string $sitename
* @param string $username
* @return NULL|boolean from notification() and email() inherited
*/
function send_register_pending_eml($email, $sitename, $username) {
$body = deindent(t('
Dear %1$s,
Thank you for registering at %2$s. Your account is pending for approval by the administrator.
'));
$body = sprintf($body, $username, $sitename);
return notification(array(
'type' => "SYSTEM_EMAIL",
'to_email' => $email,
'subject'=> sprintf( t('Registration at %s'), $sitename),
'body' => $body));
}
/*
* send registration confirmation.
* It's here as a function because the mail is sent

View file

@ -1,11 +1,12 @@
<?php
/**
* @file include/xml.php
*/
/**
* @brief This class contain functions to work with XML data
* @brief This class contain methods to work with XML data
*
*/
class xml {
@ -23,15 +24,17 @@ class xml {
public static function from_array($array, &$xml, $remove_header = false, $namespaces = array(), $root = true) {
if ($root) {
foreach($array as $key => $value) {
foreach ($namespaces AS $nskey => $nsvalue)
foreach ($array as $key => $value) {
foreach ($namespaces AS $nskey => $nsvalue) {
$key .= " xmlns".($nskey == "" ? "":":").$nskey.'="'.$nsvalue.'"';
}
if (is_array($value)) {
$root = new SimpleXMLElement("<".$key."/>");
self::from_array($value, $root, $remove_header, $namespaces, false);
} else
} else {
$root = new SimpleXMLElement("<".$key.">".xmlify($value)."</".$key.">");
}
$dom = dom_import_simplexml($root)->ownerDocument;
$dom->formatOutput = true;
@ -39,16 +42,18 @@ class xml {
$xml_text = $dom->saveXML();
if ($remove_header)
if ($remove_header) {
$xml_text = trim(substr($xml_text, 21));
}
return $xml_text;
}
}
foreach($array as $key => $value) {
if (!isset($element) AND isset($xml))
if (!isset($element) AND isset($xml)) {
$element = $xml;
}
if (is_integer($key)) {
if (isset($element)) {
@ -62,27 +67,31 @@ class xml {
}
$element_parts = explode(":", $key);
if ((count($element_parts) > 1) AND isset($namespaces[$element_parts[0]]))
if ((count($element_parts) > 1) AND isset($namespaces[$element_parts[0]])) {
$namespace = $namespaces[$element_parts[0]];
elseif (isset($namespaces[""])) {
} elseif (isset($namespaces[""])) {
$namespace = $namespaces[""];
} else
} else {
$namespace = NULL;
}
// Remove undefined namespaces from the key
if ((count($element_parts) > 1) AND is_null($namespace))
if ((count($element_parts) > 1) AND is_null($namespace)) {
$key = $element_parts[1];
}
if (substr($key, 0, 11) == "@attributes") {
if (!isset($element) OR !is_array($value))
if (!isset($element) OR !is_array($value)) {
continue;
}
foreach ($value as $attr_key => $attr_value) {
$element_parts = explode(":", $attr_key);
if ((count($element_parts) > 1) AND isset($namespaces[$element_parts[0]]))
if ((count($element_parts) > 1) AND isset($namespaces[$element_parts[0]])) {
$namespace = $namespaces[$element_parts[0]];
else
} else {
$namespace = NULL;
}
$element->addAttribute($attr_key, $attr_value, $namespace);
}
@ -90,9 +99,9 @@ class xml {
continue;
}
if (!is_array($value))
if (!is_array($value)) {
$element = $xml->addChild($key, xmlify($value), $namespace);
elseif (is_array($value)) {
} elseif (is_array($value)) {
$element = $xml->addChild($key, NULL, $namespace);
self::from_array($value, $element, $remove_header, $namespaces, false);
}
@ -111,8 +120,9 @@ class xml {
$target->addChild($elementname, xmlify($source));
else {
$child = $target->addChild($elementname);
foreach ($source->children() AS $childfield => $childentry)
foreach ($source->children() AS $childfield => $childentry) {
self::copy($childentry, $child, $childfield);
}
}
}
@ -168,11 +178,11 @@ class xml {
return(null);
}
if (!is_string($xml_element) &&
!is_array($xml_element) &&
(get_class($xml_element) == 'SimpleXMLElement')) {
$xml_element_copy = $xml_element;
$xml_element = get_object_vars($xml_element);
if (!is_string($xml_element)
&& !is_array($xml_element)
&& (get_class($xml_element) == 'SimpleXMLElement')) {
$xml_element_copy = $xml_element;
$xml_element = get_object_vars($xml_element);
}
if (is_array($xml_element)) {
@ -181,7 +191,7 @@ class xml {
return (trim(strval($xml_element_copy)));
}
foreach($xml_element as $key=>$value) {
foreach ($xml_element as $key => $value) {
$recursion_depth++;
$result_array[strtolower($key)] =
@ -223,10 +233,12 @@ class xml {
*
* @return array The parsed XML in an array form. Use print_r() to see the resulting array structure.
*/
public static function to_array($contents, $namespaces = true, $get_attributes=1, $priority = 'attribute') {
if(!$contents) return array();
public static function to_array($contents, $namespaces = true, $get_attributes = 1, $priority = 'attribute') {
if (!$contents) {
return array();
}
if(!function_exists('xml_parser_create')) {
if (!function_exists('xml_parser_create')) {
logger('xml::to_array: parser function missing');
return array();
}
@ -235,12 +247,13 @@ class xml {
libxml_use_internal_errors(true);
libxml_clear_errors();
if($namespaces)
if ($namespaces) {
$parser = @xml_parser_create_ns("UTF-8",':');
else
} else {
$parser = @xml_parser_create();
}
if(! $parser) {
if (! $parser) {
logger('xml::to_array: xml_parser_create: no resource');
return array();
}
@ -252,10 +265,11 @@ class xml {
@xml_parse_into_struct($parser, trim($contents), $xml_values);
@xml_parser_free($parser);
if(! $xml_values) {
if (! $xml_values) {
logger('xml::to_array: libxml: parse error: ' . $contents, LOGGER_DATA);
foreach(libxml_get_errors() as $err)
foreach (libxml_get_errors() as $err) {
logger('libxml: parse: ' . $err->code . " at " . $err->line . ":" . $err->column . " : " . $err->message, LOGGER_DATA);
}
libxml_clear_errors();
return;
}
@ -270,8 +284,8 @@ class xml {
// Go through the tags.
$repeated_tag_index = array(); // Multiple tags with same name will be turned into an array
foreach($xml_values as $data) {
unset($attributes,$value); // Remove existing values, or there will be trouble
foreach ($xml_values as $data) {
unset($attributes, $value); // Remove existing values, or there will be trouble
// This command will extract these variables into the foreach scope
// tag(string), type(string), level(int), attributes(array).
@ -280,46 +294,54 @@ class xml {
$result = array();
$attributes_data = array();
if(isset($value)) {
if($priority == 'tag') $result = $value;
else $result['value'] = $value; // Put the value in a assoc array if we are in the 'Attribute' mode
if (isset($value)) {
if ($priority == 'tag') {
$result = $value;
} else {
$result['value'] = $value; // Put the value in a assoc array if we are in the 'Attribute' mode
}
}
//Set the attributes too.
if(isset($attributes) and $get_attributes) {
foreach($attributes as $attr => $val) {
if($priority == 'tag') $attributes_data[$attr] = $val;
else $result['@attributes'][$attr] = $val; // Set all the attributes in a array called 'attr'
if (isset($attributes) and $get_attributes) {
foreach ($attributes as $attr => $val) {
if($priority == 'tag') {
$attributes_data[$attr] = $val;
} else {
$result['@attributes'][$attr] = $val; // Set all the attributes in a array called 'attr'
}
}
}
// See tag status and do the needed.
if($namespaces && strpos($tag,':')) {
$namespc = substr($tag,0,strrpos($tag,':'));
$tag = strtolower(substr($tag,strlen($namespc)+1));
if ($namespaces && strpos($tag, ':')) {
$namespc = substr($tag, 0, strrpos($tag, ':'));
$tag = strtolower(substr($tag, strlen($namespc)+1));
$result['@namespace'] = $namespc;
}
$tag = strtolower($tag);
if($type == "open") { // The starting of the tag '<tag>'
if ($type == "open") { // The starting of the tag '<tag>'
$parent[$level-1] = &$current;
if(!is_array($current) or (!in_array($tag, array_keys($current)))) { // Insert New tag
if (!is_array($current) or (!in_array($tag, array_keys($current)))) { // Insert New tag
$current[$tag] = $result;
if($attributes_data) $current[$tag. '_attr'] = $attributes_data;
if ($attributes_data) {
$current[$tag. '_attr'] = $attributes_data;
}
$repeated_tag_index[$tag.'_'.$level] = 1;
$current = &$current[$tag];
} else { // There was another element with the same tag name
if(isset($current[$tag][0])) { // If there is a 0th element it is already an array
if (isset($current[$tag][0])) { // If there is a 0th element it is already an array
$current[$tag][$repeated_tag_index[$tag.'_'.$level]] = $result;
$repeated_tag_index[$tag.'_'.$level]++;
} else { // This section will make the value an array if multiple tags with the same name appear together
$current[$tag] = array($current[$tag],$result); // This will combine the existing item and the new item together to make an array
$current[$tag] = array($current[$tag], $result); // This will combine the existing item and the new item together to make an array
$repeated_tag_index[$tag.'_'.$level] = 2;
if(isset($current[$tag.'_attr'])) { // The attribute of the last(0th) tag must be moved as well
if (isset($current[$tag.'_attr'])) { // The attribute of the last(0th) tag must be moved as well
$current[$tag]['0_attr'] = $current[$tag.'_attr'];
unset($current[$tag.'_attr']);
}
@ -329,35 +351,37 @@ class xml {
$current = &$current[$tag][$last_item_index];
}
} elseif($type == "complete") { // Tags that ends in 1 line '<tag />'
} elseif ($type == "complete") { // Tags that ends in 1 line '<tag />'
//See if the key is already taken.
if(!isset($current[$tag])) { //New Key
if (!isset($current[$tag])) { //New Key
$current[$tag] = $result;
$repeated_tag_index[$tag.'_'.$level] = 1;
if($priority == 'tag' and $attributes_data) $current[$tag. '_attr'] = $attributes_data;
if ($priority == 'tag' and $attributes_data) {
$current[$tag. '_attr'] = $attributes_data;
}
} else { // If taken, put all things inside a list(array)
if(isset($current[$tag][0]) and is_array($current[$tag])) { // If it is already an array...
if (isset($current[$tag][0]) and is_array($current[$tag])) { // If it is already an array...
// ...push the new element into that array.
$current[$tag][$repeated_tag_index[$tag.'_'.$level]] = $result;
if($priority == 'tag' and $get_attributes and $attributes_data) {
if ($priority == 'tag' and $get_attributes and $attributes_data) {
$current[$tag][$repeated_tag_index[$tag.'_'.$level] . '_attr'] = $attributes_data;
}
$repeated_tag_index[$tag.'_'.$level]++;
} else { // If it is not an array...
$current[$tag] = array($current[$tag],$result); //...Make it an array using using the existing value and the new value
$current[$tag] = array($current[$tag], $result); //...Make it an array using using the existing value and the new value
$repeated_tag_index[$tag.'_'.$level] = 1;
if($priority == 'tag' and $get_attributes) {
if(isset($current[$tag.'_attr'])) { // The attribute of the last(0th) tag must be moved as well
if ($priority == 'tag' and $get_attributes) {
if (isset($current[$tag.'_attr'])) { // The attribute of the last(0th) tag must be moved as well
$current[$tag]['0_attr'] = $current[$tag.'_attr'];
unset($current[$tag.'_attr']);
}
if($attributes_data) {
if ($attributes_data) {
$current[$tag][$repeated_tag_index[$tag.'_'.$level] . '_attr'] = $attributes_data;
}
}
@ -365,12 +389,25 @@ class xml {
}
}
} elseif($type == 'close') { // End of tag '</tag>'
} elseif ($type == 'close') { // End of tag '</tag>'
$current = &$parent[$level-1];
}
}
return($xml_array);
}
/**
* @brief Delete a node in a XML object
*
* @param object $doc XML document
* @param string $node Node name
*/
public static function deleteNode(&$doc, $node) {
$xpath = new DomXPath($doc);
$list = $xpath->query("//".$node);
foreach ($list as $child) {
$child->parentNode->removeChild($child);
}
}
}
?>

View file

@ -99,6 +99,10 @@ if (!$a->is_backend()) {
$stamp1 = microtime(true);
session_start();
$a->save_timestamp($stamp1, "parser");
} else {
require_once "include/poller.php";
call_worker_if_idle();
}
/**

View file

@ -296,7 +296,7 @@ function string2bb(element) {
$.fn.bbco_autocomplete = function(type) {
if(type=='bbcode') {
var open_close_elements = ['bold', 'italic', 'underline', 'overline', 'strike', 'quote', 'code', 'spoiler', 'map', 'img', 'url', 'audio', 'video', 'youtube', 'vimeo', 'list', 'ul', 'ol', 'li', 'table', 'tr', 'th', 'td', 'center', 'color', 'font', 'size', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'nobb', 'noparse', 'pre', 'abstract'];
var open_close_elements = ['bold', 'italic', 'underline', 'overline', 'strike', 'quote', 'code', 'spoiler', 'map', 'img', 'url', 'audio', 'video', 'embed', 'youtube', 'vimeo', 'list', 'ul', 'ol', 'li', 'table', 'tr', 'th', 'td', 'center', 'color', 'font', 'size', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'nobb', 'noparse', 'pre', 'abstract'];
var open_elements = ['*', 'hr'];
var elements = open_close_elements.concat(open_elements);

View file

@ -44,7 +44,7 @@ aStates[20]="|Brestskaya (Brest)|Homyel'skaya (Homyel')|Horad Minsk|Hrodzyenskay
aStates[21]="|Antwerpen|Brabant Wallon|Brussels Capitol Region|Hainaut|Liege|Limburg|Luxembourg|Namur|Oost-Vlaanderen|Vlaams Brabant|West-Vlaanderen";
aStates[22]="|Belize|Cayo|Corozal|Orange Walk|Stann Creek|Toledo";
aStates[23]="|Alibori|Atakora|Atlantique|Borgou|Collines|Couffo|Donga|Littoral|Mono|Oueme|Plateau|Zou";
aStates[24]="|Devonshire|Hamilton|Hamilton|Paget|Pembroke|Saint George|Saint Georges|Sandys|Smiths|Southampton|Warwick";
aStates[24]="|Devonshire|Hamilton (City)|Hamilton|Paget|Pembroke|Saint George|Saint Georges|Sandys|Smiths|Southampton|Warwick";
aStates[25]="|Bumthang|Chhukha|Chirang|Daga|Geylegphug|Ha|Lhuntshi|Mongar|Paro|Pemagatsel|Punakha|Samchi|Samdrup Jongkhar|Shemgang|Tashigang|Thimphu|Tongsa|Wangdi Phodrang";
aStates[26]="|Beni|Chuquisaca|Cochabamba|La Paz|Oruro|Pando|Potosi|Santa Cruz|Tarija";
aStates[27]="|Federation of Bosnia and Herzegovina|Republika Srpska";
@ -125,7 +125,7 @@ aStates[99]="|Holy See (Vatican City)"
aStates[100]="|Atlantida|Choluteca|Colon|Comayagua|Copan|Cortes|El Paraiso|Francisco Morazan|Gracias a Dios|Intibuca|Islas de la Bahia|La Paz|Lempira|Ocotepeque|Olancho|Santa Barbara|Valle|Yoro";
aStates[101]="|Hong Kong";
aStates[102]="|Howland Island";
aStates[103]="|Bacs-Kiskun|Baranya|Bekes|Bekescsaba|Borsod-Abauj-Zemplen|Budapest|Csongrad|Debrecen|Dunaujvaros|Eger|Fejer|Gyor|Gyor-Moson-Sopron|Hajdu-Bihar|Heves|Hodmezovasarhely|Jasz-Nagykun-Szolnok|Kaposvar|Kecskemet|Komarom-Esztergom|Miskolc|Nagykanizsa|Nograd|Nyiregyhaza|Pecs|Pest|Somogy|Sopron|Szabolcs-Szatmar-Bereg|Szeged|Szekesfehervar|Szolnok|Szombathely|Tatabanya|Tolna|Vas|Veszprem|Veszprem|Zala|Zalaegerszeg";
aStates[103]="|Bacs-Kiskun|Baranya|Bekes|Bekescsaba|Borsod-Abauj-Zemplen|Budapest|Csongrad|Debrecen|Dunaujvaros|Eger|Fejer|Gyor|Gyor-Moson-Sopron|Hajdu-Bihar|Heves|Hodmezovasarhely|Jasz-Nagykun-Szolnok|Kaposvar|Kecskemet|Komarom-Esztergom|Miskolc|Nagykanizsa|Nograd|Nyiregyhaza|Pecs|Pest|Somogy|Sopron|Szabolcs-Szatmar-Bereg|Szeged|Szekesfehervar|Szolnok|Szombathely|Tatabanya|Tolna|Vas|Veszprem|Veszprem (City)|Zala|Zalaegerszeg";
aStates[104]="|Akranes|Akureyri|Arnessysla|Austur-Bardhastrandarsysla|Austur-Hunavatnssysla|Austur-Skaftafellssysla|Borgarfjardharsysla|Dalasysla|Eyjafjardharsysla|Gullbringusysla|Hafnarfjordhur|Husavik|Isafjordhur|Keflavik|Kjosarsysla|Kopavogur|Myrasysla|Neskaupstadhur|Nordhur-Isafjardharsysla|Nordhur-Mulasys-la|Nordhur-Thingeyjarsysla|Olafsfjordhur|Rangarvallasysla|Reykjavik|Saudharkrokur|Seydhisfjordhur|Siglufjordhur|Skagafjardharsysla|Snaefellsnes-og Hnappadalssysla|Strandasysla|Sudhur-Mulasysla|Sudhur-Thingeyjarsysla|Vesttmannaeyjar|Vestur-Bardhastrandarsysla|Vestur-Hunavatnssysla|Vestur-Isafjardharsysla|Vestur-Skaftafellssysla";
aStates[105]="|Andaman and Nicobar Islands|Andhra Pradesh|Arunachal Pradesh|Assam|Bihar|Chandigarh|Chhattisgarh|Dadra and Nagar Haveli|Daman and Diu|Delhi|Goa|Gujarat|Haryana|Himachal Pradesh|Jammu and Kashmir|Jharkhand|Karnataka|Kerala|Lakshadweep|Madhya Pradesh|Maharashtra|Manipur|Meghalaya|Mizoram|Nagaland|Orissa|Pondicherry|Punjab|Rajasthan|Sikkim|Tamil Nadu|Tripura|Uttar Pradesh|Uttaranchal|West Bengal";
aStates[106]="|Aceh|Bali|Banten|Bengkulu|East Timor|Gorontalo|Irian Jaya|Jakarta Raya|Jambi|Jawa Barat|Jawa Tengah|Jawa Timur|Kalimantan Barat|Kalimantan Selatan|Kalimantan Tengah|Kalimantan Timur|Kepulauan Bangka Belitung|Lampung|Maluku|Maluku Utara|Nusa Tenggara Barat|Nusa Tenggara Timur|Riau|Sulawesi Selatan|Sulawesi Tengah|Sulawesi Tenggara|Sulawesi Utara|Sumatera Barat|Sumatera Selatan|Sumatera Utara|Yogyakarta";
@ -145,12 +145,12 @@ aStates[119]="|'Amman|Ajlun|Al 'Aqabah|Al Balqa'|Al Karak|Al Mafraq|At Tafilah|A
aStates[120]="|Juan de Nova Island";
aStates[121]="|Almaty|Aqmola|Aqtobe|Astana|Atyrau|Batys Qazaqstan|Bayqongyr|Mangghystau|Ongtustik Qazaqstan|Pavlodar|Qaraghandy|Qostanay|Qyzylorda|Shyghys Qazaqstan|Soltustik Qazaqstan|Zhambyl";
aStates[122]="|Central|Coast|Eastern|Nairobi Area|North Eastern|Nyanza|Rift Valley|Western";
aStates[123]="|Abaiang|Abemama|Aranuka|Arorae|Banaba|Banaba|Beru|Butaritari|Central Gilberts|Gilbert Islands|Kanton|Kiritimati|Kuria|Line Islands|Line Islands|Maiana|Makin|Marakei|Nikunau|Nonouti|Northern Gilberts|Onotoa|Phoenix Islands|Southern Gilberts|Tabiteuea|Tabuaeran|Tamana|Tarawa|Tarawa|Teraina";
aStates[123]="|Abaiang|Abemama|Aranuka|Arorae|Banaba (District)|Banaba|Beru|Butaritari|Central Gilberts (District)|Gilbert Islands (Unit)|Kanton|Kiritimati|Kuria|Line Islands (District)|Line Islands (Unit)|Maiana|Makin|Marakei|Nikunau|Nonouti|Northern Gilberts (District)|Onotoa|Phoenix Islands (Unit)|Southern Gilberts (District)|Tabiteuea|Tabuaeran|Tamana|Tarawa (District)|Tarawa|Teraina";
aStates[124]="|Chagang-do (Chagang Province)|Hamgyong-bukto (North Hamgyong Province)|Hamgyong-namdo (South Hamgyong Province)|Hwanghae-bukto (North Hwanghae Province)|Hwanghae-namdo (South Hwanghae Province)|Kaesong-si (Kaesong City)|Kangwon-do (Kangwon Province)|Namp'o-si (Namp'o City)|P'yongan-bukto (North P'yongan Province)|P'yongan-namdo (South P'yongan Province)|P'yongyang-si (P'yongyang City)|Yanggang-do (Yanggang Province)"
aStates[125]="|Ch'ungch'ong-bukto|Ch'ungch'ong-namdo|Cheju-do|Cholla-bukto|Cholla-namdo|Inch'on-gwangyoksi|Kangwon-do|Kwangju-gwangyoksi|Kyonggi-do|Kyongsang-bukto|Kyongsang-namdo|Pusan-gwangyoksi|Soul-t'ukpyolsi|Taegu-gwangyoksi|Taejon-gwangyoksi|Ulsan-gwangyoksi";
aStates[126]="|Al 'Asimah|Al Ahmadi|Al Farwaniyah|Al Jahra'|Hawalli";
aStates[127]="|Batken Oblasty|Bishkek Shaary|Chuy Oblasty (Bishkek)|Jalal-Abad Oblasty|Naryn Oblasty|Osh Oblasty|Talas Oblasty|Ysyk-Kol Oblasty (Karakol)"
aStates[128]="|Attapu|Bokeo|Bolikhamxai|Champasak|Houaphan|Khammouan|Louangnamtha|Louangphabang|Oudomxai|Phongsali|Salavan|Savannakhet|Viangchan|Viangchan|Xaignabouli|Xaisomboun|Xekong|Xiangkhoang";
aStates[128]="|Attapu|Bokeo|Bolikhamxai|Champasak|Houaphan|Khammouan|Louangnamtha|Louangphabang|Oudomxai|Phongsali|Salavan|Savannakhet|Viangchan City|Viangchan|Xaignabouli|Xaisomboun|Xekong|Xiangkhoang";
aStates[129]="|Aizkraukles Rajons|Aluksnes Rajons|Balvu Rajons|Bauskas Rajons|Cesu Rajons|Daugavpils|Daugavpils Rajons|Dobeles Rajons|Gulbenes Rajons|Jekabpils Rajons|Jelgava|Jelgavas Rajons|Jurmala|Kraslavas Rajons|Kuldigas Rajons|Leipaja|Liepajas Rajons|Limbazu Rajons|Ludzas Rajons|Madonas Rajons|Ogres Rajons|Preilu Rajons|Rezekne|Rezeknes Rajons|Riga|Rigas Rajons|Saldus Rajons|Talsu Rajons|Tukuma Rajons|Valkas Rajons|Valmieras Rajons|Ventspils|Ventspils Rajons";
aStates[130]="|Beyrouth|Ech Chimal|Ej Jnoub|El Bekaa|Jabal Loubnane";
aStates[131]="|Berea|Butha-Buthe|Leribe|Mafeteng|Maseru|Mohales Hoek|Mokhotlong|Qacha's Nek|Quthing|Thaba-Tseka";
@ -176,7 +176,7 @@ aStates[150]="|Mayotte";
aStates[151]="|Aguascalientes|Baja California|Baja California Sur|Campeche|Chiapas|Chihuahua|Coahuila de Zaragoza|Colima|Distrito Federal|Durango|Guanajuato|Guerrero|Hidalgo|Jalisco|Mexico|Michoacan de Ocampo|Morelos|Nayarit|Nuevo Leon|Oaxaca|Puebla|Queretaro de Arteaga|Quintana Roo|San Luis Potosi|Sinaloa|Sonora|Tabasco|Tamaulipas|Tlaxcala|Veracruz-Llave|Yucatan|Zacatecas";
aStates[152]="|Chuuk (Truk)|Kosrae|Pohnpei|Yap";
aStates[153]="|Midway Islands";
aStates[154]="|Balti|Cahul|Chisinau|Chisinau|Dubasari|Edinet|Gagauzia|Lapusna|Orhei|Soroca|Tighina|Ungheni";
aStates[154]="|Balti|Cahul|Chisinau (City)|Chisinau|Dubasari|Edinet|Gagauzia|Lapusna|Orhei|Soroca|Tighina|Ungheni";
aStates[155]="|Fontvieille|La Condamine|Monaco-Ville|Monte-Carlo";
aStates[156]="|Arhangay|Bayan-Olgiy|Bayanhongor|Bulgan|Darhan|Dornod|Dornogovi|Dundgovi|Dzavhan|Erdenet|Govi-Altay|Hentiy|Hovd|Hovsgol|Omnogovi|Ovorhangay|Selenge|Suhbaatar|Tov|Ulaanbaatar|Uvs";
aStates[157]="|Saint Anthony|Saint Georges|Saint Peter's";
@ -243,7 +243,7 @@ aStates[217]="|Hhohho|Lubombo|Manzini|Shiselweni";
aStates[218]="|Blekinge|Dalarnas|Gavleborgs|Gotlands|Hallands|Jamtlands|Jonkopings|Kalmar|Kronobergs|Norrbottens|Orebro|Ostergotlands|Skane|Sodermanlands|Stockholms|Uppsala|Varmlands|Vasterbottens|Vasternorrlands|Vastmanlands|Vastra Gotalands";
aStates[219]="|Aargau|Ausser-Rhoden|Basel-Landschaft|Basel-Stadt|Bern|Fribourg|Geneve|Glarus|Graubunden|Inner-Rhoden|Jura|Luzern|Neuchatel|Nidwalden|Obwalden|Sankt Gallen|Schaffhausen|Schwyz|Solothurn|Thurgau|Ticino|Uri|Valais|Vaud|Zug|Zurich";
aStates[220]="|Al Hasakah|Al Ladhiqiyah|Al Qunaytirah|Ar Raqqah|As Suwayda'|Dar'a|Dayr az Zawr|Dimashq|Halab|Hamah|Hims|Idlib|Rif Dimashq|Tartus";
aStates[221]="|Chang-hua|Chi-lung|Chia-i|Chia-i|Chung-hsing-hsin-ts'un|Hsin-chu|Hsin-chu|Hua-lien|I-lan|Kao-hsiung|Kao-hsiung|Miao-li|Nan-t'ou|P'eng-hu|P'ing-tung|T'ai-chung|T'ai-chung|T'ai-nan|T'ai-nan|T'ai-pei|T'ai-pei|T'ai-tung|T'ao-yuan|Yun-lin";
aStates[221]="|Chang-hua|Chi-lung|Chia-i (City)|Chia-i|Chung-hsing-hsin-ts'un|Hsin-chu (City)|Hsin-chu|Hua-lien|I-lan|Kao-hsiung (City)|Kao-hsiung|Miao-li|Nan-t'ou|P'eng-hu|P'ing-tung|T'ai-chung (City)|T'ai-chung|T'ai-nan (City)|T'ai-nan|T'ai-pei (City)|T'ai-pei|T'ai-tung|T'ao-yuan|Yun-lin";
aStates[222]="|Viloyati Khatlon|Viloyati Leninobod|Viloyati Mukhtori Kuhistoni Badakhshon";
aStates[223]="|Arusha|Dar es Salaam|Dodoma|Iringa|Kagera|Kigoma|Kilimanjaro|Lindi|Mara|Mbeya|Morogoro|Mtwara|Mwanza|Pemba North|Pemba South|Pwani|Rukwa|Ruvuma|Shinyanga|Singida|Tabora|Tanga|Zanzibar Central/South|Zanzibar North|Zanzibar Urban/West";
aStates[224]="|Amnat Charoen|Ang Thong|Buriram|Chachoengsao|Chai Nat|Chaiyaphum|Chanthaburi|Chiang Mai|Chiang Rai|Chon Buri|Chumphon|Kalasin|Kamphaeng Phet|Kanchanaburi|Khon Kaen|Krabi|Krung Thep Mahanakhon (Bangkok)|Lampang|Lamphun|Loei|Lop Buri|Mae Hong Son|Maha Sarakham|Mukdahan|Nakhon Nayok|Nakhon Pathom|Nakhon Phanom|Nakhon Ratchasima|Nakhon Sawan|Nakhon Si Thammarat|Nan|Narathiwat|Nong Bua Lamphu|Nong Khai|Nonthaburi|Pathum Thani|Pattani|Phangnga|Phatthalung|Phayao|Phetchabun|Phetchaburi|Phichit|Phitsanulok|Phra Nakhon Si Ayutthaya|Phrae|Phuket|Prachin Buri|Prachuap Khiri Khan|Ranong|Ratchaburi|Rayong|Roi Et|Sa Kaeo|Sakon Nakhon|Samut Prakan|Samut Sakhon|Samut Songkhram|Sara Buri|Satun|Sing Buri|Sisaket|Songkhla|Sukhothai|Suphan Buri|Surat Thani|Surin|Tak|Trang|Trat|Ubon Ratchathani|Udon Thani|Uthai Thani|Uttaradit|Yala|Yasothon";

View file

@ -5,36 +5,38 @@
function _resizeIframe(obj, desth) {
var h = obj.style.height;
var ch = obj.contentWindow.document.body.scrollHeight + 'px';
if (h==ch) {
var ch = obj.contentWindow.document.body.scrollHeight;
if (h == (ch + 'px')) {
return;
}
//console.log("_resizeIframe", obj, desth, ch);
if (desth!=ch) {
setTimeout(_resizeIframe, 500, obj, ch);
} else {
if (ch>0) obj.style.height = ch;
setTimeout(_resizeIframe, 1000, obj, ch);
if (desth == ch && ch>0) {
obj.style.height = ch + 'px';
}
setTimeout(_resizeIframe, 100, obj, ch);
}
function openClose(theID) {
if(document.getElementById(theID).style.display == "block") {
document.getElementById(theID).style.display = "none"
}
else {
document.getElementById(theID).style.display = "block"
}
}
function openClose(theID) {
if(document.getElementById(theID).style.display == "block") {
document.getElementById(theID).style.display = "none"
}
else {
document.getElementById(theID).style.display = "block"
}
}
function openMenu(theID) {
document.getElementById(theID).style.display = "block"
}
function openMenu(theID) {
document.getElementById(theID).style.display = "block"
}
function closeMenu(theID) {
document.getElementById(theID).style.display = "none"
}
function closeMenu(theID) {
document.getElementById(theID).style.display = "none"
}
function decodeHtml(html) {
var txt = document.createElement("textarea");
txt.innerHTML = html;
return txt.value;
}
var src = null;
@ -90,7 +92,6 @@
/* event from comment textarea button popups */
/* insert returned bbcode at cursor position or replace selected text */
$("body").on("fbrowser.image.comment", function(e, filename, bbcode, id) {
console.log("on", id);
$.colorbox.close();
var textarea = document.getElementById("comment-edit-text-" +id);
var start = textarea.selectionStart;
@ -115,7 +116,6 @@
$("#"+id+"_onoff ."+ (val==0?"on":"off")).addClass("hidden");
$("#"+id+"_onoff ."+ (val==1?"on":"off")).removeClass("hidden");
input.val(val);
//console.log(id);
});
/* setup field_richtext */
@ -125,6 +125,7 @@
function close_last_popup_menu() {
if(last_popup_menu) {
last_popup_menu.hide();
last_popup_menu.off('click', function(e) {e.stopPropagation()});
last_popup_button.removeClass("selected");
last_popup_menu = null;
last_popup_button = null;
@ -147,6 +148,7 @@
last_popup_button = null;
} else {
last_popup_menu = menu;
last_popup_menu.on('click', function(e) {e.stopPropagation()});
last_popup_button = parent;
$('#nav-notifications-menu').perfectScrollbar('update');
}
@ -175,110 +177,77 @@
$('#nav-notifications-menu, aside').perfectScrollbar();
/* nav update event */
$('nav').bind('nav-update', function(e,data){
var invalid = $(data).find('invalid').text();
$('nav').bind('nav-update', function(e, data){
var invalid = data.invalid || 0;
if(invalid == 1) { window.location.href=window.location.href }
var net = $(data).find('net').text();
if(net == 0) { net = ''; $('#net-update').removeClass('show') } else { $('#net-update').addClass('show') }
$('#net-update').html(net);
['net', 'home', 'intro', 'mail', 'events', 'birthdays', 'notify'].forEach(function(type) {
var number = data[type];
if (number == 0) {
number = '';
$('#' + type + '-update').removeClass('show');
} else {
$('#' + type + '-update').addClass('show');
}
$('#' + type + '-update').text(number);
});
var home = $(data).find('home').text();
if(home == 0) { home = ''; $('#home-update').removeClass('show') } else { $('#home-update').addClass('show') }
$('#home-update').html(home);
var intro = $(data).find('intro').text();
if(intro == 0) { intro = ''; $('#intro-update').removeClass('show') } else { $('#intro-update').addClass('show') }
$('#intro-update').html(intro);
var mail = $(data).find('mail').text();
if(mail == 0) { mail = ''; $('#mail-update').removeClass('show') } else { $('#mail-update').addClass('show') }
$('#mail-update').html(mail);
var intro = $(data).find('intro').text();
if(intro == 0) { intro = ''; $('#intro-update-li').removeClass('show') } else { $('#intro-update-li').addClass('show') }
var intro = data['intro'];
if(intro == 0) { intro = ''; $('#intro-update-li').removeClass('show') } else { $('#intro-update-li').addClass('show') }
$('#intro-update-li').html(intro);
var mail = $(data).find('mail').text();
if(mail == 0) { mail = ''; $('#mail-update-li').removeClass('show') } else { $('#mail-update-li').addClass('show') }
var mail = data['mail'];
if(mail == 0) { mail = ''; $('#mail-update-li').removeClass('show') } else { $('#mail-update-li').addClass('show') }
$('#mail-update-li').html(mail);
var allevents = $(data).find('all-events').text();
if(allevents == 0) { allevents = ''; $('#allevents-update').removeClass('show') } else { $('#allevents-update').addClass('show') }
$('#allevents-update').html(allevents);
var alleventstoday = $(data).find('all-events-today').text();
if(alleventstoday == 0) { $('#allevents-update').removeClass('notif-allevents-today') } else { $('#allevents-update').addClass('notif-allevents-today') }
var events = $(data).find('events').text();
if(events == 0) { events = ''; $('#events-update').removeClass('show') } else { $('#events-update').addClass('show') }
$('#events-update').html(events);
var eventstoday = $(data).find('events-today').text();
if(eventstoday == 0) { $('#events-update').removeClass('notif-events-today') } else { $('#events-update').addClass('notif-events-today') }
var birthdays = $(data).find('birthdays').text();
if(birthdays == 0) {birthdays = ''; $('#birthdays-update').removeClass('show') } else { $('#birthdays-update').addClass('show') }
$('#birthdays-update').html(birthdays);
var birthdaystoday = $(data).find('birthdays-today').text();
if(birthdaystoday == 0) { $('#birthdays-update').removeClass('notif-birthdays-today') } else { $('#birthdays-update').addClass('notif-birthdays-today') }
$(".sidebar-group-li .notify").removeClass("show");
$(data).find("group").each(function() {
var gid = this.id;
var gcount = this.innerHTML;
$(data.groups).each(function(key, group) {
var gid = group.id;
var gcount = group.count;
$(".group-"+gid+" .notify").addClass("show").text(gcount);
});
$(".forum-widget-entry .notify").removeClass("show");
$(data).find("forum").each(function() {
var fid = this.id;
var fcount = this.innerHTML;
$(data.forums).each(function(key, forum) {
var fid = forum.id;
var fcount = forum.count;
$(".forum-"+fid+" .notify").addClass("show").text(fcount);
});
var eNotif = $(data).find('notif')
if (eNotif.children("note").length==0){
if (data.notifications.length == 0) {
$("#nav-notifications-menu").html(notifications_empty);
} else {
nnm = $("#nav-notifications-menu");
var nnm = $("#nav-notifications-menu");
nnm.html(notifications_all + notifications_mark);
//nnm.attr('popup','true');
var notification_lastitem = parseInt(localStorage.getItem("notification-lastitem"));
var notification_id = 0;
eNotif.children("note").each(function(){
e = $(this);
var text = e.text().format("<span class='contactname'>"+e.attr('name')+"</span>");
var contact = ("<a href="+e.attr('url')+"><span class='contactname'>"+e.attr('name')+"</span></a>");
var seenclass = (e.attr('seen')==1)?"notify-seen":"notify-unseen";
$(data.notifications).each(function(key, notif){
var text = notif.message.format('<span class="contactname">' + notif.name + '</span>');
var contact = ('<a href="' + notif.url + '"><span class="contactname">' + notif.name + '</span></a>');
var seenclass = (notif.seen == 1) ? "notify-seen" : "notify-unseen";
var html = notifications_tpl.format(
e.attr('href'), // {0} // link to the source
e.attr('photo'), // {1} // photo of the contact
text, // {2} // preformatet text (autor + text)
e.attr('date'), // {3} // date of notification (time ago)
seenclass, // {4} // vistiting status of the notification
new Date(e.attr('timestamp')*1000), // {5} //date of notification
e.attr('url'), // {6} // profile url of the contact
e.text().format(""), // {7} // clean status text
contact // {8} //preformatat author (name + profile url)
notif.href, // {0} // link to the source
notif.photo, // {1} // photo of the contact
text, // {2} // preformatted text (autor + text)
notif.date, // {3} // date of notification (time ago)
seenclass, // {4} // visited status of the notification
new Date(notif.timestamp*1000), // {5} // date of notification
notif.url, // {6} // profile url of the contact
notif.message.format(contact), // {7} // preformatted html (text including author profile url)
'' // {8} // Deprecated
);
nnm.append(html);
});
$(eNotif.children("note").get().reverse()).each(function(){
e = $(this);
notification_id = parseInt(e.attr('timestamp'));
if (notification_lastitem!== null && notification_id > notification_lastitem) {
if (getNotificationPermission()==="granted") {
$(data.notifications.reverse()).each(function(key, e){
notification_id = parseInt(e.timestamp);
if (notification_lastitem !== null && notification_id > notification_lastitem) {
if (getNotificationPermission() === "granted") {
var notification = new Notification(document.title, {
body: e.text().replace('&rarr; ','').format(e.attr('name')),
icon: e.attr('photo'),
body: decodeHtml(e.message.replace('&rarr; ', '').format(e.name)),
icon: e.photo,
});
notification['url'] = e.attr('href');
notification['url'] = e.href;
notification.addEventListener("click", function(ev){
window.location = ev.target.url;
});
@ -299,23 +268,18 @@
});
}
notif = eNotif.attr('count');
if (notif>0){
var notif = data['notify'];
if (notif > 0){
$("#nav-notifications-linkmenu").addClass("on");
} else {
$("#nav-notifications-linkmenu").removeClass("on");
}
if(notif == 0) { notif = ''; $('#notify-update').removeClass('show') } else { $('#notify-update').addClass('show') }
$('#notify-update').html(notif);
var eSysmsg = $(data).find('sysmsgs');
eSysmsg.children("notice").each(function(){
text = $(this).text();
$.jGrowl(text, { sticky: true, theme: 'notice' });
$(data.sysmsgs.notice).each(function(key, message){
$.jGrowl(message, {sticky: true, theme: 'notice'});
});
eSysmsg.children("info").each(function(){
text = $(this).text();
$.jGrowl(text, { sticky: false, theme: 'info', life: 5000 });
$(data.sysmsgs.info).each(function(key, message){
$.jGrowl(message, {sticky: false, theme: 'info', life: 5000});
});
/* update the js scrollbars */
@ -370,50 +334,38 @@
function NavUpdate() {
if(! stopped) {
var pingCmd = 'ping' + ((localUser != 0) ? '?f=&uid=' + localUser : '');
$.get(pingCmd,function(data) {
$(data).find('result').each(function() {
if (!stopped) {
var pingCmd = 'ping?format=json' + ((localUser != 0) ? '&f=&uid=' + localUser : '');
$.get(pingCmd, function(data) {
if (data.result) {
// send nav-update event
$('nav').trigger('nav-update', this);
$('nav').trigger('nav-update', data.result);
// start live update
if($('#live-network').length) { src = 'network'; liveUpdate(); }
if($('#live-profile').length) { src = 'profile'; liveUpdate(); }
if($('#live-community').length) { src = 'community'; liveUpdate(); }
if($('#live-notes').length) { src = 'notes'; liveUpdate(); }
if($('#live-display').length) { src = 'display'; liveUpdate(); }
/* if($('#live-display').length) {
if(liking) {
liking = 0;
window.location.href=window.location.href
['network', 'profile', 'community', 'notes', 'display'].forEach(function (src) {
if ($('#live-' + src).length) {
liveUpdate(src);
}
}*/
if($('#live-photos').length) {
if(liking) {
});
if ($('#live-photos').length) {
if (liking) {
liking = 0;
window.location.href=window.location.href
window.location.href = window.location.href;
}
}
});
}
}) ;
}
timer = setTimeout(NavUpdate,updateInterval);
timer = setTimeout(NavUpdate, updateInterval);
}
function liveUpdate() {
function liveUpdate(src) {
if((src == null) || (stopped) || (! profile_uid)) { $('.like-rotator').hide(); return; }
if(($('.comment-edit-text-full').length) || (in_progress)) {
if(livetime) {
clearTimeout(livetime);
}
livetime = setTimeout(liveUpdate, 5000);
livetime = setTimeout(function() {liveUpdate(src)}, 5000);
return;
}
if(livetime != null)
@ -736,8 +688,6 @@
// page number
infinite_scroll.pageno+=1;
console.log('Loading page ' + infinite_scroll.pageno);
// get the raw content from the next page and insert this content
// right before "#conversation-end"
$.get('network?mode=raw' + infinite_scroll.reload_uri + '&page=' + infinite_scroll.pageno, function(data) {

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,127 @@
## Reporting Bugs
Each bug report MUST have a [JSFiddle/JSBin] recreation before any work can begin. [further instructions &raquo;](http://fullcalendar.io/wiki/Reporting-Bugs/)
## Requesting Features
Please search the [Issue Tracker] to see if your feature has already been requested, and if so, subscribe to it. Otherwise, read these [further instructions &raquo;](http://fullcalendar.io/wiki/Requesting-Features/)
## Contributing Features
The FullCalendar project welcomes [Pull Requests][Using Pull Requests] for new features, but because there are so many feature requests (over 100), and because every new feature requires refinement and maintenance, each PR will be prioritized against the project's other demands and might take a while to make it to an official release.
Furthermore, each new feature should be designed as robustly as possible and be useful beyond the immediate usecase it was initially designed for. Feel free to start a ticket discussing the feature's specs before coding.
## Contributing Bugfixes
In the description of your [Pull Request][Using Pull Requests], please include recreation steps for the bug as well as a [JSFiddle/JSBin] demo. Communicating the buggy behavior is a requirement before a merge can happen.
## Contributing Locales
Please edit the original files in the `locale/` directory. DO NOT edit anything in the `dist/` directory. The build system will responsible for merging FullCalendar's `locale/` data with the [MomentJS locale data].
## Other Ways to Contribute
[Read about other ways to contribute &raquo;](http://fullcalendar.io/wiki/Contributing/)
## Getting Set Up
You will need [Git][git], [Node][node], and NPM installed. For clarification, please view the [jQuery readme][jq-readme], which requires a similar setup.
Also, you will need the [gulp-cli][gulp-cli] package installed globally (`-g`) on your system:
npm install -g gulp-cli
Then, clone FullCalendar's git repo:
git clone git://github.com/fullcalendar/fullcalendar.git
Enter the directory and install FullCalendar's dependencies:
cd fullcalendar
npm install
## What to edit
When modifying files, please do not edit the generated or minified files in the `dist/` directory. Please edit the original `src/` files.
## Development Workflow
After you make code changes, you'll want to compile the JS/CSS so that it can be previewed from the tests and demos. You can either manually rebuild each time you make a change:
gulp dev
Or, you can run a script that automatically rebuilds whenever you save a source file:
gulp watch
When you are finished, run the following command to write the distributable files into the `./dist/` directory:
gulp dist
If you want to clean up the generated files, run:
gulp clean
## Style Guide
Please follow the [Google JavaScript Style Guide] as closely as possible. With the following exceptions:
```js
if (true) {
}
else { // please put else, else if, and catch on a separate line
}
// please write one-line array literals with a one-space padding inside
var a = [ 1, 2, 3 ];
// please write one-line object literals with a one-space padding inside
var o = { a: 1, b: 2, c: 3 };
```
Other exceptions:
- please ignore anything about Google Closure Compiler or the `goog` library
- please do not write JSDoc comments
Notes about whitespace:
- **use *tabs* instead of spaces**
- separate functions with *2* blank lines
- separate logical blocks within functions with *1* blank line
Run the command line tool to automatically check your style:
gulp lint
## Before Submitting your Code
If you have edited code (including **tests** and **translations**) and would like to submit a pull request, please make sure you have done the following:
1. Conformed to the style guide (successfully run `gulp lint`)
2. Written automated tests. View the [Automated Test Readme]
[JSFiddle/JSBin]: http://fullcalendar.io/wiki/Reporting-Bugs/
[Issue Tracker]: https://github.com/fullcalendar/fullcalendar/issues
[Using Pull Requests]: https://help.github.com/articles/using-pull-requests/
[MomentJS locale data]: https://github.com/moment/moment/tree/develop/locale
[git]: http://git-scm.com/
[node]: http://nodejs.org/
[gulp-cli]: https://github.com/gulpjs/gulp/blob/master/docs/getting-started.md
[jq-readme]: https://github.com/jquery/jquery/blob/master/README.md#what-you-need-to-build-your-own-jquery
[Google JavaScript Style Guide]: http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
[Automated Test Readme]: https://github.com/fullcalendar/fullcalendar/wiki/Automated-Tests

View file

@ -1,4 +1,4 @@
Copyright (c) 2013 Adam Shaw
Copyright (c) 2015 Adam Shaw
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View file

@ -1,382 +0,0 @@
version 1.6.4 (9/1/13)
- better algorithm for positioning timed agenda events (issue 1115)
- `slotEventOverlap` option to tweak timed agenda event overlapping (issue 218)
- selection bug when slot height is customized (issue 1035)
- supply view argument in `loading` callback (issue 1018)
- fixed week number not displaying in agenda views (issue 1951)
- fixed fullCalendar not initializing with no options (issue 1356)
- NPM's package.json, no more warnings or errors (issue 1762)
- building the bower component should output bower.json instead of component.json (PR 125)
- use bower internally for fetching new versions of jQuery and jQuery UI
version 1.6.3 (8/10/13)
- viewRender callback (PR 15)
- viewDestroy callback (PR 15)
- eventDestroy callback (PR 111)
- handleWindowResize option (PR 54)
- eventStartEditable/startEditable options (PR 49)
- eventDurationEditable/durationEditable options (PR 49)
- specify function for $.ajax `data` parameter for JSON event sources (PR 59)
- fixed bug with agenda event dropping in wrong column (PR 55)
- easier event element z-index customization (PR 58)
- classNames on past/future days (PR 88)
- allow null/undefined event titles (PR 84)
- small optimize for agenda event rendering (PR 56)
- deprecated:
- viewDisplay
- disableDragging
- disableResizing
- bundled with latest jQuery (1.10.2) and jQuery UI (1.10.3)
version 1.6.2 (7/18/13)
- hiddenDays option (issue 686)
- bugfix: when eventRender returns false, incorrect stacking of events (issue 762)
- bugfix: couldn't change event.backgroundImage when calling updateEvent (thx stephenharris)
version 1.6.1 (4/14/13)
- fixed event inner content overflow bug (issue 1783)
- fixed table header className bug (1772)
- removed text-shadow on events (better for general use, thx tkrotoff)
version 1.6.0 (3/18/13)
- visual facelift, with bootstrap-inspired buttons and colors
- simplified HTML/CSS for events and buttons
- dayRender, for modifying a day cell (issue 191, thx althaus)
- week numbers on side of calendar (issue 295)
- weekNumber
- weekNumberCalculation
- weekNumberTitle
- "W" formatting variable
- finer snapping granularity for agenda view events (issue 495, thx ms-doodle-com)
- eventAfterAllRender (issue 753, thx pdrakeweb)
- eventDataTransform (thx joeyspo)
- data-date attributes on cells (thx Jae)
- expose $.fullCalendar.dateFormatters
- when clicking fast on buttons, prevent text selection
- bundled with latest jQuery (1.9.1) and jQuery UI (1.10.2)
- Grunt/Lumbar build system for internal development
- build for Bower package manager
- build for jQuery plugin site
version 1.5.4 (9/5/12)
- made compatible with jQuery 1.8.* (thx archaeron)
- bundled with jQuery 1.8.1 and jQuery UI 1.8.23
version 1.5.3 (2/6/12)
- fixed dragging issue with jQuery UI 1.8.16 (issue 1168)
- bundled with jQuery 1.7.1 and jQuery UI 1.8.17
version 1.5.2 (8/21/11)
- correctly process UTC "Z" ISO8601 date strings (issue 750)
version 1.5.1 (4/9/11)
- more flexible ISO8601 date parsing (issue 814)
- more flexible parsing of UNIX timestamps (issue 826)
- FullCalendar now buildable from source on a Mac (issue 795)
- FullCalendar QA'd in FF4 (issue 883)
- upgraded to jQuery 1.5.2 (which supports IE9) and jQuery UI 1.8.11
version 1.5 (3/19/11)
- slicker default styling for buttons
- reworked a lot of the calendar's HTML and accompanying CSS
(solves issues 327 and 395)
- more printer-friendly (fullcalendar-print.css)
- fullcalendar now inherits styles from jquery-ui themes differently.
styles for buttons are distinct from styles for calendar cells.
(solves issue 299)
- can now color events through FullCalendar options and Event-Object properties (issue 117)
THIS IS NOW THE PREFERRED METHOD OF COLORING EVENTS (as opposed to using className and CSS)
- FullCalendar options:
- eventColor (changes both background and border)
- eventBackgroundColor
- eventBorderColor
- eventTextColor
- Event-Object options:
- color (changes both background and border)
- backgroundColor
- borderColor
- textColor
- can now specify an event source as an *object* with a `url` property (json feed) or
an `events` property (function or array) with additional properties that will
be applied to the entire event source:
- color (changes both background and border)
- backgroudColor
- borderColor
- textColor
- className
- editable
- allDayDefault
- ignoreTimezone
- startParam (for a feed)
- endParam (for a feed)
- ANY OF THE JQUERY $.ajax OPTIONS
allows for easily changing from GET to POST and sending additional parameters (issue 386)
allows for easily attaching ajax handlers such as `error` (issue 754)
allows for turning caching on (issue 355)
- Google Calendar feeds are now specified differently:
- specify a simple string of your feed's URL
- specify an *object* with a `url` property of your feed's URL.
you can include any of the new Event-Source options in this object.
- the old `$.fullCalendar.gcalFeed` method still works
- no more IE7 SSL popup (issue 504)
- remove `cacheParam` - use json event source `cache` option instead
- latest jquery/jquery-ui
version 1.4.11 (2/22/11)
- fixed rerenderEvents bug (issue 790)
- fixed bug with faulty dragging of events from all-day slot in agenda views
- bundled with jquery 1.5 and jquery-ui 1.8.9
version 1.4.10 (1/2/11)
- fixed bug with resizing event to different week in 5-day month view (issue 740)
- fixed bug with events not sticking after a removeEvents call (issue 757)
- fixed bug with underlying parseTime method, and other uses of parseInt (issue 688)
version 1.4.9 (11/16/10)
- new algorithm for vertically stacking events (issue 111)
- resizing an event to a different week (issue 306)
- bug: some events not rendered with consecutive calls to addEventSource (issue 679)
version 1.4.8 (10/16/10)
- ignoreTimezone option (set to `false` to process UTC offsets in ISO8601 dates)
- bugfixes
- event refetching not being called under certain conditions (issues 417, 554)
- event refetching being called multiple times under certain conditions (issues 586, 616)
- selection cannot be triggered by right mouse button (issue 558)
- agenda view left axis sized incorrectly (issue 465)
- IE js error when calendar is too narrow (issue 517)
- agenda view looks strange when no scrollbars (issue 235)
- improved parsing of ISO8601 dates with UTC offsets
- $.fullCalendar.version
- an internal refactor of the code, for easier future development and modularity
version 1.4.7 (7/5/10)
- "dropping" external objects onto the calendar
- droppable (boolean, to turn on/off)
- dropAccept (to filter which events the calendar will accept)
- drop (trigger)
- selectable options can now be specified with a View Option Hash
- bugfixes
- dragged & reverted events having wrong time text (issue 406)
- bug rendering events that have an endtime with seconds, but no hours/minutes (issue 477)
- gotoDate date overflow bug (issue 429)
- wrong date reported when clicking on edge of last column in agenda views (412)
- support newlines in event titles
- select/unselect callbacks now passes native js event
version 1.4.6 (5/31/10)
- "selecting" days or timeslots
- options: selectable, selectHelper, unselectAuto, unselectCancel
- callbacks: select, unselect
- methods: select, unselect
- when dragging an event, the highlighting reflects the duration of the event
- code compressing by Google Closure Compiler
- bundled with jQuery 1.4.2 and jQuery UI 1.8.1
version 1.4.5 (2/21/10)
- lazyFetching option, which can force the calendar to fetch events on every view/date change
- scroll state of agenda views are preserved when switching back to view
- bugfixes
- calling methods on an uninitialized fullcalendar throws error
- IE6/7 bug where an entire view becomes invisible (issue 320)
- error when rendering a hidden calendar (in jquery ui tabs for example) in IE (issue 340)
- interconnected bugs related to calendar resizing and scrollbars
- when switching views or clicking prev/next, calendar would "blink" (issue 333)
- liquid-width calendar's events shifted (depending on initial height of browser) (issue 341)
- more robust underlying algorithm for calendar resizing
version 1.4.4 (2/3/10)
- optimized event rendering in all views (events render in 1/10 the time)
- gotoDate() does not force the calendar to unnecessarily rerender
- render() method now correctly readjusts height
version 1.4.3 (12/22/09)
- added destroy method
- Google Calendar event pages respect currentTimezone
- caching now handled by jQuery's ajax
- protection from setting aspectRatio to zero
- bugfixes
- parseISO8601 and DST caused certain events to display day before
- button positioning problem in IE6
- ajax event source removed after recently being added, events still displayed
- event not displayed when end is an empty string
- dynamically setting calendar height when no events have been fetched, throws error
version 1.4.2 (12/02/09)
- eventAfterRender trigger
- getDate & getView methods
- height & contentHeight options (explicitly sets the pixel height)
- minTime & maxTime options (restricts shown hours in agenda view)
- getters [for all options] and setters [for height, contentHeight, and aspectRatio ONLY! stay tuned..]
- render method now readjusts calendar's size
- bugfixes
- lightbox scripts that use iframes (like fancybox)
- day-of-week classNames were off when firstDay=1
- guaranteed space on right side of agenda events (even when stacked)
- accepts ISO8601 dates with a space (instead of 'T')
version 1.4.1 (10/31/09)
- can exclude weekends with new 'weekends' option
- gcal feed 'currentTimezone' option
- bugfixes
- year/month/date option sometimes wouldn't set correctly (depending on current date)
- daylight savings issue caused agenda views to start at 1am (for BST users)
- cleanup of gcal.js code
version 1.4 (10/19/09)
- agendaWeek and agendaDay views
- added some options for agenda views:
- allDaySlot
- allDayText
- firstHour
- slotMinutes
- defaultEventMinutes
- axisFormat
- modified some existing options/triggers to work with agenda views:
- dragOpacity and timeFormat can now accept a "View Hash" (a new concept)
- dayClick now has an allDay parameter
- eventDrop now has an an allDay parameter
(this will affect those who use revertFunc, adjust parameter list)
- added 'prevYear' and 'nextYear' for buttons in header
- minor change for theme users, ui-state-hover not applied to active/inactive buttons
- added event-color-changing example in docs
- better defaults for right-to-left themed button icons
version 1.3.2 (10/13/09)
- Bugfixes (please upgrade from 1.3.1!)
- squashed potential infinite loop when addMonths and addDays
is called with an invalid date
- $.fullCalendar.parseDate() now correctly parses IETF format
- when switching views, the 'today' button sticks inactive, fixed
- gotoDate now can accept a single Date argument
- documentation for changes in 1.3.1 and 1.3.2 now on website
version 1.3.1 (9/30/09)
- Important Bugfixes (please upgrade from 1.3!)
- When current date was late in the month, for long months, and prev/next buttons
were clicked in month-view, some months would be skipped/repeated
- In certain time zones, daylight savings time would cause certain days
to be misnumbered in month-view
- Subtle change in way week interval is chosen when switching from month to basicWeek/basicDay view
- Added 'allDayDefault' option
- Added 'changeView' and 'render' methods
version 1.3 (9/21/09)
- different 'views': month/basicWeek/basicDay
- more flexible 'header' system for buttons
- themable by jQuery UI themes
- resizable events (require jQuery UI resizable plugin)
- rescoped & rewritten CSS, enhanced default look
- cleaner css & rendering techniques for right-to-left
- reworked options & API to support multiple views / be consistent with jQuery UI
- refactoring of entire codebase
- broken into different JS & CSS files, assembled w/ build scripts
- new test suite for new features, uses firebug-lite
- refactored docs
- Options
+ date
+ defaultView
+ aspectRatio
+ disableResizing
+ monthNames (use instead of $.fullCalendar.monthNames)
+ monthNamesShort (use instead of $.fullCalendar.monthAbbrevs)
+ dayNames (use instead of $.fullCalendar.dayNames)
+ dayNamesShort (use instead of $.fullCalendar.dayAbbrevs)
+ theme
+ buttonText
+ buttonIcons
x draggable -> editable/disableDragging
x fixedWeeks -> weekMode
x abbrevDayHeadings -> columnFormat
x buttons/title -> header
x eventDragOpacity -> dragOpacity
x eventRevertDuration -> dragRevertDuration
x weekStart -> firstDay
x rightToLeft -> isRTL
x showTime (use 'allDay' CalEvent property instead)
- Triggered Actions
+ eventResizeStart
+ eventResizeStop
+ eventResize
x monthDisplay -> viewDisplay
x resize -> windowResize
'eventDrop' params changed, can revert if ajax cuts out
- CalEvent Properties
x showTime -> allDay
x draggable -> editable
'end' is now INCLUSIVE when allDay=true
'url' now produces a real <a> tag, more native clicking/tab behavior
- Methods:
+ renderEvent
x prevMonth -> prev
x nextMonth -> next
x prevYear/nextYear -> moveDate
x refresh -> rerenderEvents/refetchEvents
x removeEvent -> removeEvents
x getEventsByID -> clientEvents
- Utilities:
'formatDate' format string completely changed (inspired by jQuery UI datepicker + datejs)
'formatDates' added to support date-ranges
- Google Calendar Options:
x draggable -> editable
- Bugfixes
- gcal extension fetched 25 results max, now fetches all
version 1.2.1 (6/29/09)
- bugfixes
- allows and corrects invalid end dates for events
- doesn't throw an error in IE while rendering when display:none
- fixed 'loading' callback when used w/ multiple addEventSource calls
- gcal className can now be an array
version 1.2 (5/31/09)
- expanded API
- 'className' CalEvent attribute
- 'source' CalEvent attribute
- dynamically get/add/remove/update events of current month
- locale improvements: change month/day name text
- better date formatting ($.fullCalendar.formatDate)
- multiple 'event sources' allowed
- dynamically add/remove event sources
- options for prevYear and nextYear buttons
- docs have been reworked (include addition of Google Calendar docs)
- changed behavior of parseDate for number strings
(now interpets as unix timestamp, not MS times)
- bugfixes
- rightToLeft month start bug
- off-by-one errors with month formatting commands
- events from previous months sticking when clicking prev/next quickly
- Google Calendar API changed to work w/ multiple event sources
- can also provide 'className' and 'draggable' options
- date utilties moved from $ to $.fullCalendar
- more documentation in source code
- minified version of fullcalendar.js
- test suit (available from svn)
- top buttons now use <button> w/ an inner <span> for better css cusomization
- thus CSS has changed. IF UPGRADING FROM PREVIOUS VERSIONS,
UPGRADE YOUR FULLCALENDAR.CSS FILE!!!
version 1.1 (5/10/09)
- Added the following options:
- weekStart
- rightToLeft
- titleFormat
- timeFormat
- cacheParam
- resize
- Fixed rendering bugs
- Opera 9.25 (events placement & window resizing)
- IE6 (window resizing)
- Optimized window resizing for ALL browsers
- Events on same day now sorted by start time (but first by timespan)
- Correct z-index when dragging
- Dragging contained in overflow DIV for IE6
- Modified fullcalendar.css
- for right-to-left support
- for variable start-of-week
- for IE6 resizing bug
- for THEAD and TBODY (in 1.0, just used TBODY, restructured in 1.1)
- IF UPGRADING FROM FULLCALENDAR 1.0, YOU MUST UPGRADE FULLCALENDAR.CSS
!!!!!!!!!!!

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
/*!
* FullCalendar v1.6.4 Print Stylesheet
* Docs & License: http://arshaw.com/fullcalendar/
* (c) 2013 Adam Shaw
* FullCalendar v3.0.1 Print Stylesheet
* Docs & License: http://fullcalendar.io/
* (c) 2016 Adam Shaw
*/
/*
@ -10,23 +10,199 @@
* Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css.
*/
.fc {
max-width: 100% !important;
}
/* Events
-----------------------------------------------------*/
/* Global Event Restyling
--------------------------------------------------------------------------------------------------*/
.fc-event {
background: #fff !important;
color: #000 !important;
}
page-break-inside: avoid;
}
/* for vertical events */
.fc-event .fc-resizer {
display: none;
}
.fc-event-bg {
/* Table & Day-Row Restyling
--------------------------------------------------------------------------------------------------*/
.fc th,
.fc td,
.fc hr,
.fc thead,
.fc tbody,
.fc-row {
border-color: #ccc !important;
background: #fff !important;
}
/* kill the overlaid, absolutely-positioned components */
/* common... */
.fc-bg,
.fc-bgevent-skeleton,
.fc-highlight-skeleton,
.fc-helper-skeleton,
/* for timegrid. within cells within table skeletons... */
.fc-bgevent-container,
.fc-business-container,
.fc-highlight-container,
.fc-helper-container {
display: none;
}
/* don't force a min-height on rows (for DayGrid) */
.fc tbody .fc-row {
height: auto !important; /* undo height that JS set in distributeHeight */
min-height: 0 !important; /* undo the min-height from each view's specific stylesheet */
}
.fc tbody .fc-row .fc-content-skeleton {
position: static; /* undo .fc-rigid */
padding-bottom: 0 !important; /* use a more border-friendly method for this... */
}
.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td { /* only works in newer browsers */
padding-bottom: 1em; /* ...gives space within the skeleton. also ensures min height in a way */
}
.fc tbody .fc-row .fc-content-skeleton table {
/* provides a min-height for the row, but only effective for IE, which exaggerates this value,
making it look more like 3em. for other browers, it will already be this tall */
height: 1em;
}
/* Undo month-view event limiting. Display all events and hide the "more" links
--------------------------------------------------------------------------------------------------*/
.fc-more-cell,
.fc-more {
display: none !important;
}
}
.fc-event .ui-resizable-handle {
display: none !important;
}
.fc tr.fc-limited {
display: table-row !important;
}
.fc td.fc-limited {
display: table-cell !important;
}
.fc-popover {
display: none; /* never display the "more.." popover in print mode */
}
/* TimeGrid Restyling
--------------------------------------------------------------------------------------------------*/
/* undo the min-height 100% trick used to fill the container's height */
.fc-time-grid {
min-height: 0 !important;
}
/* don't display the side axis at all ("all-day" and time cells) */
.fc-agenda-view .fc-axis {
display: none;
}
/* don't display the horizontal lines */
.fc-slats,
.fc-time-grid hr { /* this hr is used when height is underused and needs to be filled */
display: none !important; /* important overrides inline declaration */
}
/* let the container that holds the events be naturally positioned and create real height */
.fc-time-grid .fc-content-skeleton {
position: static;
}
/* in case there are no events, we still want some height */
.fc-time-grid .fc-content-skeleton table {
height: 4em;
}
/* kill the horizontal spacing made by the event container. event margins will be done below */
.fc-time-grid .fc-event-container {
margin: 0 !important;
}
/* TimeGrid *Event* Restyling
--------------------------------------------------------------------------------------------------*/
/* naturally position events, vertically stacking them */
.fc-time-grid .fc-event {
position: static !important;
margin: 3px 2px !important;
}
/* for events that continue to a future day, give the bottom border back */
.fc-time-grid .fc-event.fc-not-end {
border-bottom-width: 1px !important;
}
/* indicate the event continues via "..." text */
.fc-time-grid .fc-event.fc-not-end:after {
content: "...";
}
/* for events that are continuations from previous days, give the top border back */
.fc-time-grid .fc-event.fc-not-start {
border-top-width: 1px !important;
}
/* indicate the event is a continuation via "..." text */
.fc-time-grid .fc-event.fc-not-start:before {
content: "...";
}
/* time */
/* undo a previous declaration and let the time text span to a second line */
.fc-time-grid .fc-event .fc-time {
white-space: normal !important;
}
/* hide the the time that is normally displayed... */
.fc-time-grid .fc-event .fc-time span {
display: none;
}
/* ...replace it with a more verbose version (includes AM/PM) stored in an html attribute */
.fc-time-grid .fc-event .fc-time:after {
content: attr(data-full);
}
/* Vertical Scroller & Containers
--------------------------------------------------------------------------------------------------*/
/* kill the scrollbars and allow natural height */
.fc-scroller,
.fc-day-grid-container, /* these divs might be assigned height, which we need to cleared */
.fc-time-grid-container { /* */
overflow: visible !important;
height: auto !important;
}
/* kill the horizontal border/padding used to compensate for scrollbars */
.fc-row {
border: 0 !important;
margin: 0 !important;
}
/* Button Controls
--------------------------------------------------------------------------------------------------*/
.fc-button-group,
.fc button {
display: none; /* don't display any button-related controls */
}

View file

@ -1,107 +1,180 @@
/*!
* FullCalendar v1.6.4 Google Calendar Plugin
* Docs & License: http://arshaw.com/fullcalendar/
* (c) 2013 Adam Shaw
* FullCalendar v3.0.1 Google Calendar Plugin
* Docs & License: http://fullcalendar.io/
* (c) 2016 Adam Shaw
*/
(function($) {
(function(factory) {
if (typeof define === 'function' && define.amd) {
define([ 'jquery' ], factory);
}
else if (typeof exports === 'object') { // Node/CommonJS
module.exports = factory(require('jquery'));
}
else {
factory(jQuery);
}
})(function($) {
var fc = $.fullCalendar;
var formatDate = fc.formatDate;
var parseISO8601 = fc.parseISO8601;
var addDays = fc.addDays;
var applyAll = fc.applyAll;
var API_BASE = 'https://www.googleapis.com/calendar/v3/calendars';
var FC = $.fullCalendar;
var applyAll = FC.applyAll;
fc.sourceNormalizers.push(function(sourceOptions) {
if (sourceOptions.dataType == 'gcal' ||
sourceOptions.dataType === undefined &&
(sourceOptions.url || '').match(/^(http|https):\/\/www.google.com\/calendar\/feeds\//)) {
sourceOptions.dataType = 'gcal';
if (sourceOptions.editable === undefined) {
sourceOptions.editable = false;
}
FC.sourceNormalizers.push(function(sourceOptions) {
var googleCalendarId = sourceOptions.googleCalendarId;
var url = sourceOptions.url;
var match;
// if the Google Calendar ID hasn't been explicitly defined
if (!googleCalendarId && url) {
// detect if the ID was specified as a single string.
// will match calendars like "asdf1234@calendar.google.com" in addition to person email calendars.
if (/^[^\/]+@([^\/\.]+\.)*(google|googlemail|gmail)\.com$/.test(url)) {
googleCalendarId = url;
}
});
// try to scrape it out of a V1 or V3 API feed URL
else if (
(match = /^https:\/\/www.googleapis.com\/calendar\/v3\/calendars\/([^\/]*)/.exec(url)) ||
(match = /^https?:\/\/www.google.com\/calendar\/feeds\/([^\/]*)/.exec(url))
) {
googleCalendarId = decodeURIComponent(match[1]);
}
if (googleCalendarId) {
sourceOptions.googleCalendarId = googleCalendarId;
}
}
fc.sourceFetchers.push(function(sourceOptions, start, end) {
if (sourceOptions.dataType == 'gcal') {
return transformOptions(sourceOptions, start, end);
if (googleCalendarId) { // is this a Google Calendar?
// make each Google Calendar source uneditable by default
if (sourceOptions.editable == null) {
sourceOptions.editable = false;
}
// We want removeEventSource to work, but it won't know about the googleCalendarId primitive.
// Shoehorn it into the url, which will function as the unique primitive. Won't cause side effects.
// This hack is obsolete since 2.2.3, but keep it so this plugin file is compatible with old versions.
sourceOptions.url = googleCalendarId;
}
});
function transformOptions(sourceOptions, start, end) {
FC.sourceFetchers.push(function(sourceOptions, start, end, timezone) {
if (sourceOptions.googleCalendarId) {
return transformOptions(sourceOptions, start, end, timezone, this); // `this` is the calendar
}
});
function transformOptions(sourceOptions, start, end, timezone, calendar) {
var url = API_BASE + '/' + encodeURIComponent(sourceOptions.googleCalendarId) + '/events?callback=?'; // jsonp
var apiKey = sourceOptions.googleCalendarApiKey || calendar.options.googleCalendarApiKey;
var success = sourceOptions.success;
var data = $.extend({}, sourceOptions.data || {}, {
'start-min': formatDate(start, 'u'),
'start-max': formatDate(end, 'u'),
'singleevents': true,
'max-results': 9999
var data;
var timezoneArg; // populated when a specific timezone. escaped to Google's liking
function reportError(message, apiErrorObjs) {
var errorObjs = apiErrorObjs || [ { message: message } ]; // to be passed into error handlers
// call error handlers
(sourceOptions.googleCalendarError || $.noop).apply(calendar, errorObjs);
(calendar.options.googleCalendarError || $.noop).apply(calendar, errorObjs);
// print error to debug console
FC.warn.apply(null, [ message ].concat(apiErrorObjs || []));
}
if (!apiKey) {
reportError("Specify a googleCalendarApiKey. See http://fullcalendar.io/docs/google_calendar/");
return {}; // an empty source to use instead. won't fetch anything.
}
// The API expects an ISO8601 datetime with a time and timezone part.
// Since the calendar's timezone offset isn't always known, request the date in UTC and pad it by a day on each
// side, guaranteeing we will receive all events in the desired range, albeit a superset.
// .utc() will set a zone and give it a 00:00:00 time.
if (!start.hasZone()) {
start = start.clone().utc().add(-1, 'day');
}
if (!end.hasZone()) {
end = end.clone().utc().add(1, 'day');
}
// when sending timezone names to Google, only accepts underscores, not spaces
if (timezone && timezone != 'local') {
timezoneArg = timezone.replace(' ', '_');
}
data = $.extend({}, sourceOptions.data || {}, {
key: apiKey,
timeMin: start.format(),
timeMax: end.format(),
timeZone: timezoneArg,
singleEvents: true,
maxResults: 9999
});
var ctz = sourceOptions.currentTimezone;
if (ctz) {
data.ctz = ctz = ctz.replace(' ', '_');
}
return $.extend({}, sourceOptions, {
url: sourceOptions.url.replace(/\/basic$/, '/full') + '?alt=json-in-script&callback=?',
dataType: 'jsonp',
googleCalendarId: null, // prevents source-normalizing from happening again
url: url,
data: data,
startParam: false,
endParam: false,
startParam: false, // `false` omits this parameter. we already included it above
endParam: false, // same
timezoneParam: false, // same
success: function(data) {
var events = [];
if (data.feed.entry) {
$.each(data.feed.entry, function(i, entry) {
var startStr = entry['gd$when'][0]['startTime'];
var start = parseISO8601(startStr, true);
var end = parseISO8601(entry['gd$when'][0]['endTime'], true);
var allDay = startStr.indexOf('T') == -1;
var url;
$.each(entry.link, function(i, link) {
if (link.type == 'text/html') {
url = link.href;
if (ctz) {
url += (url.indexOf('?') == -1 ? '?' : '&') + 'ctz=' + ctz;
}
}
});
if (allDay) {
addDays(end, -1); // make inclusive
var successArgs;
var successRes;
if (data.error) {
reportError('Google Calendar API: ' + data.error.message, data.error.errors);
}
else if (data.items) {
$.each(data.items, function(i, entry) {
var url = entry.htmlLink || null;
// make the URLs for each event show times in the correct timezone
if (timezoneArg && url !== null) {
url = injectQsComponent(url, 'ctz=' + timezoneArg);
}
events.push({
id: entry['gCal$uid']['value'],
title: entry['title']['$t'],
id: entry.id,
title: entry.summary,
start: entry.start.dateTime || entry.start.date, // try timed. will fall back to all-day
end: entry.end.dateTime || entry.end.date, // same
url: url,
start: start,
end: end,
allDay: allDay,
location: entry['gd$where'][0]['valueString'],
description: entry['content']['$t']
location: entry.location,
description: entry.description
});
});
// call the success handler(s) and allow it to return a new events array
successArgs = [ events ].concat(Array.prototype.slice.call(arguments, 1)); // forward other jq args
successRes = applyAll(success, this, successArgs);
if ($.isArray(successRes)) {
return successRes;
}
}
var args = [events].concat(Array.prototype.slice.call(arguments, 1));
var res = applyAll(success, this, args);
if ($.isArray(res)) {
return res;
}
return events;
}
});
}
// legacy
fc.gcalFeed = function(url, sourceOptions) {
return $.extend({}, sourceOptions, { url: url, dataType: 'gcal' });
};
// Injects a string like "arg=value" into the querystring of a URL
function injectQsComponent(url, component) {
// inject it after the querystring but before the fragment
return url.replace(/(\?.*?)?(#|$)/, function(whole, qs, hash) {
return (qs ? qs + '&' : '?') + component + hash;
});
}
})(jQuery);
});

File diff suppressed because one or more lines are too long

View file

@ -396,7 +396,7 @@ class Text_LanguageDetect
* Returns the list of detectable languages
*
* @access public
* @return array the names of the languages known to this object<<<<<<<
* @return array the names of the languages known to this object
* @throws Text_LanguageDetect_Exception
*/
function getLanguages()

Some files were not shown because too many files have changed in this diff Show more