diff --git a/boot.php b/boot.php
index 2ef5853e0..5815b8db1 100644
--- a/boot.php
+++ b/boot.php
@@ -1,4 +1,5 @@
\r\n" );
define ( 'ATOM_TIME', 'Y-m-d\TH:i:s\Z' );
-
/**
* @brief Image storage quality.
*
@@ -61,8 +62,8 @@ define ( 'ATOM_TIME', 'Y-m-d\TH:i:s\Z' );
* below about 50
*
*/
-
define ( 'JPEG_QUALITY', 100 );
+
/**
* $a->config['system']['png_quality'] from 0 (uncompressed) to 9
*/
@@ -86,12 +87,10 @@ define ( 'PNG_QUALITY', 8 );
*/
define ( 'MAX_IMAGE_LENGTH', -1 );
-
/**
* Not yet used
*/
-
-define ( 'DEFAULT_DB_ENGINE', 'MyISAM' );
+define ( 'DEFAULT_DB_ENGINE', 'InnoDB' );
/**
* @name SSL Policy
@@ -165,7 +164,6 @@ define ( 'UPDATE_SUCCESS', 0);
define ( 'UPDATE_FAILED', 1);
/** @}*/
-
/**
* @name page/profile types
*
@@ -218,6 +216,20 @@ define ( 'CP_USERS_ON_SERVER', 0 );
define ( 'CP_GLOBAL_COMMUNITY', 1 );
/** @}*/
+/**
+ * @name Protocols
+ *
+ * Different protocols that we are storing
+ * @{
+ */
+define('PROTOCOL_UNKNOWN', 0);
+define('PROTOCOL_DFRN', 1);
+define('PROTOCOL_DIASPORA', 2);
+define('PROTOCOL_OSTATUS_SALMON', 3);
+define('PROTOCOL_OSTATUS_FEED', 4);
+define('PROTOCOL_GS_CONVERSATION', 5);
+/** @}*/
+
/**
* @name Network
*
@@ -277,20 +289,16 @@ $netgroup_ids = array(
NETWORK_PHANTOM => (-127),
);
-
/**
* Maximum number of "people who like (or don't like) this" that we will list by name
*/
-
define ( 'MAX_LIKERS', 75);
/**
* Communication timeout
*/
-
define ( 'ZCURL_TIMEOUT' , (-1));
-
/**
* @name Notify
*
@@ -331,8 +339,6 @@ define ( 'TERM_CONVERSATION', 7 );
define ( 'TERM_OBJ_POST', 1 );
define ( 'TERM_OBJ_PHOTO', 2 );
-
-
/**
* @name Namespaces
*
@@ -354,6 +360,7 @@ define ( 'NAMESPACE_FEED', 'http://schemas.google.com/g/2010#updates-
define ( 'NAMESPACE_OSTATUS', 'http://ostatus.org/schema/1.0' );
define ( 'NAMESPACE_STATUSNET', 'http://status.net/schema/api/1/' );
define ( 'NAMESPACE_ATOM1', 'http://www.w3.org/2005/Atom' );
+define ( 'NAMESPACE_MASTODON', 'http://mastodon.social/schema/1.0' );
/* @}*/
/**
@@ -443,14 +450,13 @@ define('SR_SCOPE_TAGS', 'tags');
/**
* Lowest possible date time value
*/
-
define ('NULL_DATE', '0001-01-01 00:00:00');
-
// Normally this constant is defined - but not if "pcntl" isn't installed
if (!defined("SIGTERM")) {
define("SIGTERM", 15);
}
+
/**
*
* Reverse the effect of magic_quotes_gpc if it is enabled.
@@ -458,9 +464,7 @@ if (!defined("SIGTERM")) {
* See http://php.net/manual/en/security.magicquotes.disabling.php
*
*/
-
function startup() {
-
error_reporting(E_ERROR | E_WARNING | E_PARSE);
set_time_limit(0);
@@ -468,7 +472,6 @@ function startup() {
// This has to be quite large to deal with embedded private photos
ini_set('pcre.backtrack_limit', 500000);
-
if (get_magic_quotes_gpc()) {
$process = array(&$_GET, &$_POST, &$_COOKIE, &$_REQUEST);
while (list($key, $val) = each($process)) {
@@ -484,946 +487,6 @@ function startup() {
}
unset($process);
}
-
-}
-
-/**
- *
- * class: App
- *
- * @brief Our main application structure for the life of this page.
- *
- * Primarily deals with the URL that got us here
- * and tries to make some sense of it, and
- * stores our page contents and config storage
- * and anything else that might need to be passed around
- * before we spit the page out.
- *
- */
-class App {
-
- /// @TODO decide indending as a colorful mixure is ahead ...
- public $module_loaded = false;
- public $query_string;
- public $config;
- public $page;
- public $profile;
- public $profile_uid;
- public $user;
- public $cid;
- public $contact;
- public $contacts;
- public $page_contact;
- public $content;
- public $data = array();
- public $error = false;
- public $cmd;
- public $argv;
- public $argc;
- public $module;
- public $pager;
- public $strings;
- public $path;
- public $hooks;
- public $timezone;
- public $interactive = true;
- public $plugins;
- public $apps = array();
- public $identities;
- public $is_mobile = false;
- public $is_tablet = false;
- public $is_friendica_app;
- public $performance = array();
- public $callstack = array();
- public $theme_info = array();
- public $backend = true;
-
- public $nav_sel;
-
- public $category;
-
-
- // Allow themes to control internal parameters
- // by changing App values in theme.php
-
- public $sourcename = '';
- public $videowidth = 425;
- public $videoheight = 350;
- public $force_max_items = 0;
- public $theme_thread_allow = true;
- public $theme_events_in_profile = true;
-
- /**
- * @brief An array for all theme-controllable parameters
- *
- * Mostly unimplemented yet. Only options 'template_engine' and
- * beyond are used.
- */
- public $theme = array(
- 'sourcename' => '',
- 'videowidth' => 425,
- 'videoheight' => 350,
- 'force_max_items' => 0,
- 'thread_allow' => true,
- 'stylesheet' => '',
- 'template_engine' => 'smarty3',
- );
-
- /**
- * @brief An array of registered template engines ('name'=>'class name')
- */
- public $template_engines = array();
- /**
- * @brief An array of instanced template engines ('name'=>'instance')
- */
- public $template_engine_instance = array();
-
- public $process_id;
-
- private $ldelim = array(
- 'internal' => '',
- 'smarty3' => '{{'
- );
- private $rdelim = array(
- 'internal' => '',
- 'smarty3' => '}}'
- );
-
- private $scheme;
- private $hostname;
- private $db;
-
- private $curl_code;
- private $curl_content_type;
- private $curl_headers;
-
- private $cached_profile_image;
- private $cached_profile_picdate;
-
- private static $a;
-
- /**
- * @brief App constructor.
- */
- function __construct() {
-
- global $default_timezone;
-
- $hostname = "";
-
- if (file_exists(".htpreconfig.php")) {
- include ".htpreconfig.php";
- }
-
- $this->timezone = ((x($default_timezone)) ? $default_timezone : 'UTC');
-
- date_default_timezone_set($this->timezone);
-
- $this->performance["start"] = microtime(true);
- $this->performance["database"] = 0;
- $this->performance["database_write"] = 0;
- $this->performance["network"] = 0;
- $this->performance["file"] = 0;
- $this->performance["rendering"] = 0;
- $this->performance["parser"] = 0;
- $this->performance["marktime"] = 0;
- $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();
- $this->callstack["parser"] = array();
-
- $this->config = array();
- $this->page = array();
- $this->pager= array();
-
- $this->query_string = '';
-
- $this->process_id = uniqid("log", true);
-
- startup();
-
- set_include_path(
- get_include_path() . PATH_SEPARATOR
- . 'include' . PATH_SEPARATOR
- . 'library' . PATH_SEPARATOR
- . 'library/langdet' . PATH_SEPARATOR
- . '.' );
-
- $this->scheme = 'http';
-
- if ((x($_SERVER, 'HTTPS') && $_SERVER['HTTPS']) ||
- (x($_SERVER, 'HTTP_FORWARDED') && preg_match("/proto=https/", $_SERVER['HTTP_FORWARDED'])) ||
- (x($_SERVER, 'HTTP_X_FORWARDED_PROTO') && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') ||
- (x($_SERVER, 'HTTP_X_FORWARDED_SSL') && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on') ||
- (x($_SERVER, 'FRONT_END_HTTPS') && $_SERVER['FRONT_END_HTTPS'] == 'on') ||
- (x($_SERVER, 'SERVER_PORT') && (intval($_SERVER['SERVER_PORT']) == 443)) // XXX: reasonable assumption, but isn't this hardcoding too much?
- ) {
- $this->scheme = 'https';
- }
-
- if (x($_SERVER, 'SERVER_NAME')) {
- $this->hostname = $_SERVER['SERVER_NAME'];
-
- if (x($_SERVER, 'SERVER_PORT') && $_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) {
- $this->hostname .= ':' . $_SERVER['SERVER_PORT'];
- }
- /*
- * Figure out if we are running at the top of a domain
- * or in a sub-directory and adjust accordingly
- */
-
- /// @TODO This kind of escaping breaks syntax-highlightning on CoolEdit (Midnight Commander)
- $path = trim(dirname($_SERVER['SCRIPT_NAME']), '/\\');
- if (isset($path) && strlen($path) && ($path != $this->path)) {
- $this->path = $path;
- }
- }
-
- if ($hostname != "") {
- $this->hostname = $hostname;
- }
-
- if (is_array($_SERVER["argv"]) && $_SERVER["argc"] > 1 && substr(end($_SERVER["argv"]), 0, 4) == "http" ) {
- $this->set_baseurl(array_pop($_SERVER["argv"]));
- $_SERVER["argc"] --;
- }
-
- if ((x($_SERVER, 'QUERY_STRING')) && substr($_SERVER['QUERY_STRING'], 0, 9) === "pagename=") {
- $this->query_string = substr($_SERVER['QUERY_STRING'], 9);
-
- // removing trailing / - maybe a nginx problem
- $this->query_string = ltrim($this->query_string, '/');
- } elseif ((x($_SERVER, 'QUERY_STRING')) && substr($_SERVER['QUERY_STRING'], 0, 2) === "q=") {
- $this->query_string = substr($_SERVER['QUERY_STRING'], 2);
-
- // removing trailing / - maybe a nginx problem
- $this->query_string = ltrim($this->query_string, '/');
- }
-
- if (x($_GET, 'pagename')) {
- $this->cmd = trim($_GET['pagename'], '/\\');
- } elseif (x($_GET, 'q')) {
- $this->cmd = trim($_GET['q'], '/\\');
- }
-
-
- // fix query_string
- $this->query_string = str_replace($this->cmd . "&", $this->cmd . "?", $this->query_string);
-
- // unix style "homedir"
- if (substr($this->cmd, 0, 1) === '~') {
- $this->cmd = 'profile/' . substr($this->cmd, 1);
- }
-
- // Diaspora style profile url
- if (substr($this->cmd, 0, 2) === 'u/') {
- $this->cmd = 'profile/' . substr($this->cmd, 2);
- }
-
-
- /*
- * Break the URL path into C style argc/argv style arguments for our
- * modules. Given "http://example.com/module/arg1/arg2", $this->argc
- * will be 3 (integer) and $this->argv will contain:
- * [0] => 'module'
- * [1] => 'arg1'
- * [2] => 'arg2'
- *
- *
- * There will always be one argument. If provided a naked domain
- * URL, $this->argv[0] is set to "home".
- */
-
- $this->argv = explode('/', $this->cmd);
- $this->argc = count($this->argv);
- if ((array_key_exists('0', $this->argv)) && strlen($this->argv[0])) {
- $this->module = str_replace(".", "_", $this->argv[0]);
- $this->module = str_replace("-", "_", $this->module);
- } else {
- $this->argc = 1;
- $this->argv = array('home');
- $this->module = 'home';
- }
-
- /*
- * See if there is any page number information, and initialise
- * pagination
- */
-
- $this->pager['page'] = ((x($_GET, 'page') && intval($_GET['page']) > 0) ? intval($_GET['page']) : 1);
- $this->pager['itemspage'] = 50;
- $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
-
- if ($this->pager['start'] < 0) {
- $this->pager['start'] = 0;
- }
- $this->pager['total'] = 0;
-
- /*
- * Detect mobile devices
- */
-
- $mobile_detect = new Mobile_Detect();
- $this->is_mobile = $mobile_detect->isMobile();
- $this->is_tablet = $mobile_detect->isTablet();
-
- // Friendica-Client
- $this->is_friendica_app = ($_SERVER['HTTP_USER_AGENT'] == "Apache-HttpClient/UNAVAILABLE (java 1.4)");
-
- /*
- * register template engines
- */
- $dc = get_declared_classes();
- foreach ($dc as $k) {
- if (in_array("ITemplateEngine", class_implements($k))){
- $this->register_template_engine($k);
- }
- }
-
- self::$a = $this;
-
- }
-
- public static function get_basepath() {
-
- $basepath = get_config("system", "basepath");
-
- if ($basepath == "") {
- $basepath = dirname(__FILE__);
- }
-
- if ($basepath == "") {
- $basepath = $_SERVER["DOCUMENT_ROOT"];
- }
-
- if ($basepath == "") {
- $basepath = $_SERVER["PWD"];
- }
-
- return $basepath;
- }
-
- function get_scheme() {
- 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
- *
- * 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 (!(isset($this) && get_class($this) == __CLASS__)) {
- return self::$a->get_baseurl($ssl);
- }
-
- $scheme = $this->scheme;
-
- if (Config::get('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 (Config::get('system', 'ssl_policy') == SSL_POLICY_SELFSIGN) {
- if ($ssl) {
- $scheme = 'https';
- } else {
- $scheme = 'http';
- }
- }
-
- if (Config::get('config', 'hostname') != '') {
- $this->hostname = Config::get('config', 'hostname');
- }
-
- return $scheme . "://" . $this->hostname . ((isset($this->path) && strlen($this->path)) ? '/' . $this->path : '' );
- }
-
- /**
- * @brief Initializes the baseurl components
- *
- * Clears the baseurl cache to prevent inconstistencies
- *
- * @param string $url
- */
- function set_baseurl($url) {
- $parsed = @parse_url($url);
-
- if ($parsed) {
- $this->scheme = $parsed['scheme'];
-
- $hostname = $parsed['host'];
- if (x($parsed, 'port')) {
- $hostname .= ':' . $parsed['port'];
- }
- if (x($parsed, 'path')) {
- $this->path = trim($parsed['path'], '\\/');
- }
-
- if (file_exists(".htpreconfig.php")) {
- include ".htpreconfig.php";
- }
-
- if (get_config('config', 'hostname') != '') {
- $this->hostname = get_config('config', 'hostname');
- }
-
- if (!isset($this->hostname) OR ($this->hostname == '')) {
- $this->hostname = $hostname;
- }
- }
- }
-
- function get_hostname() {
- if (get_config('config', 'hostname') != "") {
- $this->hostname = get_config('config', 'hostname');
- }
-
- return $this->hostname;
- }
-
- function set_hostname($h) {
- $this->hostname = $h;
- }
-
- function set_path($p) {
- $this->path = trim(trim($p), '/');
- }
-
- function get_path() {
- return $this->path;
- }
-
- function set_pager_total($n) {
- $this->pager['total'] = intval($n);
- }
-
- function set_pager_itemspage($n) {
- $this->pager['itemspage'] = ((intval($n) > 0) ? intval($n) : 0);
- $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
- }
-
- function set_pager_page($n) {
- $this->pager['page'] = $n;
- $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
- }
-
- function init_pagehead() {
- $interval = ((local_user()) ? get_pconfig(local_user(),'system','update_interval') : 40000);
-
- // If the update is "deactivated" set it to the highest integer number (~24 days)
- if ($interval < 0) {
- $interval = 2147483647;
- }
-
- if ($interval < 10000) {
- $interval = 40000;
- }
-
- // compose the page title from the sitename and the
- // current module called
- if (!$this->module == '') {
- $this->page['title'] = $this->config['sitename'] . ' (' . $this->module . ')';
- } else {
- $this->page['title'] = $this->config['sitename'];
- }
-
- /* put the head template at the beginning of page['htmlhead']
- * since the code added by the modules frequently depends on it
- * being first
- */
- if (!isset($this->page['htmlhead'])) {
- $this->page['htmlhead'] = '';
- }
-
- // If we're using Smarty, then doing replace_macros() will replace
- // any unrecognized variables with a blank string. Since we delay
- // replacing $stylesheet until later, we need to replace it now
- // with another variable name
- if ($this->theme['template_engine'] === 'smarty3') {
- $stylesheet = $this->get_template_ldelim('smarty3') . '$stylesheet' . $this->get_template_rdelim('smarty3');
- } else {
- $stylesheet = '$stylesheet';
- }
-
- $shortcut_icon = get_config("system", "shortcut_icon");
- if ($shortcut_icon == "") {
- $shortcut_icon = "images/friendica-32.png";
- }
-
- $touch_icon = get_config("system", "touch_icon");
- if ($touch_icon == "") {
- $touch_icon = "images/friendica-128.png";
- }
-
- // get data wich is needed for infinite scroll on the network page
- $invinite_scroll = infinite_scroll_data($this->module);
-
- $tpl = get_markup_template('head.tpl');
- $this->page['htmlhead'] = replace_macros($tpl,array(
- '$baseurl' => $this->get_baseurl(), // FIXME for z_path!!!!
- '$local_user' => local_user(),
- '$generator' => 'Friendica' . ' ' . FRIENDICA_VERSION,
- '$delitem' => t('Delete this item?'),
- '$showmore' => t('show more'),
- '$showfewer' => t('show fewer'),
- '$update_interval' => $interval,
- '$shortcut_icon' => $shortcut_icon,
- '$touch_icon' => $touch_icon,
- '$stylesheet' => $stylesheet,
- '$infinite_scroll' => $invinite_scroll,
- )) . $this->page['htmlhead'];
- }
-
- function init_page_end() {
- if (!isset($this->page['end'])) {
- $this->page['end'] = '';
- }
- $tpl = get_markup_template('end.tpl');
- $this->page['end'] = replace_macros($tpl,array(
- '$baseurl' => $this->get_baseurl() // FIXME for z_path!!!!
- )) . $this->page['end'];
- }
-
- function set_curl_code($code) {
- $this->curl_code = $code;
- }
-
- function get_curl_code() {
- return $this->curl_code;
- }
-
- function set_curl_content_type($content_type) {
- $this->curl_content_type = $content_type;
- }
-
- function get_curl_content_type() {
- return $this->curl_content_type;
- }
-
- function set_curl_headers($headers) {
- $this->curl_headers = $headers;
- }
-
- function get_curl_headers() {
- return $this->curl_headers;
- }
-
- function get_cached_avatar_image($avatar_image){
- return $avatar_image;
- }
-
-
- /**
- * @brief Removes the baseurl from an url. This avoids some mixed content problems.
- *
- * @param string $orig_url
- *
- * @return string The cleaned url
- */
- function remove_baseurl($orig_url){
-
- // Is the function called statically?
- if (!(isset($this) && get_class($this) == __CLASS__)) {
- return self::$a->remove_baseurl($orig_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."/", "", $nurl);
-
- // if it is an external link return the orignal value
- if ($url == normalise_link($orig_url)) {
- return $orig_url;
- } else {
- return $url;
- }
- }
-
- /**
- * @brief Register template engine class
- *
- * If $name is "", is used class static property $class::$name
- *
- * @param string $class
- * @param string $name
- */
- function register_template_engine($class, $name = '') {
- /// @TODO Really === and not just == ?
- if ($name === "") {
- $v = get_class_vars( $class );
- if (x($v,"name")) $name = $v['name'];
- }
- if ($name === "") {
- echo "template engine $class cannot be registered without a name.\n";
- killme();
- }
- $this->template_engines[$name] = $class;
- }
-
- /**
- * @brief Return template engine instance.
- *
- * If $name is not defined, return engine defined by theme,
- * or default
- *
- * @param strin $name Template engine name
- * @return object Template Engine instance
- */
- function template_engine($name = '') {
- /// @TODO really type-check included?
- if ($name !== "") {
- $template_engine = $name;
- } else {
- $template_engine = 'smarty3';
- if (x($this->theme, 'template_engine')) {
- $template_engine = $this->theme['template_engine'];
- }
- }
-
- if (isset($this->template_engines[$template_engine])){
- if (isset($this->template_engine_instance[$template_engine])){
- return $this->template_engine_instance[$template_engine];
- } else {
- $class = $this->template_engines[$template_engine];
- $obj = new $class;
- $this->template_engine_instance[$template_engine] = $obj;
- return $obj;
- }
- }
-
- echo "template engine $template_engine is not registered!\n"; killme();
- }
-
- /**
- * @brief Returns the active template engine.
- *
- * @return string
- */
- function get_template_engine() {
- return $this->theme['template_engine'];
- }
-
- function set_template_engine($engine = 'smarty3') {
- $this->theme['template_engine'] = $engine;
- }
-
- function get_template_ldelim($engine = 'smarty3') {
- return $this->ldelim[$engine];
- }
-
- function get_template_rdelim($engine = 'smarty3') {
- return $this->rdelim[$engine];
- }
-
- 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])) {
- // Prevent ugly E_NOTICE
- $this->performance[$value] = 0;
- }
-
- $this->performance[$value] += (float)$duration;
- $this->performance["marktime"] += (float)$duration;
-
- $callstack = $this->callstack();
-
- if (!isset($this->callstack[$value][$callstack])) {
- // Prevent ugly E_NOTICE
- $this->callstack[$value][$callstack] = 0;
- }
-
- $this->callstack[$value][$callstack] += (float)$duration;
-
- }
-
- /**
- * @brief Log active processes into the "process" table
- */
- function start_process() {
- $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
-
- $command = basename($trace[0]["file"]);
-
- $this->remove_inactive_processes();
-
- q("START TRANSACTION");
-
- $r = q("SELECT `pid` FROM `process` WHERE `pid` = %d", intval(getmypid()));
- 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)) {
- q("DELETE FROM `process` WHERE `pid` = %d", intval($process["pid"]));
- }
- }
- }
- q("COMMIT");
- }
-
- /**
- * @brief Remove the active process from the "process" table
- */
- function end_process() {
- q("DELETE FROM `process` WHERE `pid` = %d", intval(getmypid()));
- }
-
- /**
- * @brief Returns a string with a callstack. Can be used for logging.
- *
- * @return string
- */
- function callstack() {
- $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 6);
-
- // We remove the first two items from the list since they contain data that we don't need.
- array_shift($trace);
- array_shift($trace);
-
- $callstack = array();
- foreach ($trace AS $func) {
- $callstack[] = $func["function"];
- }
-
- return implode(", ", $callstack);
- }
-
- function get_useragent() {
- return
- FRIENDICA_PLATFORM . " '" .
- FRIENDICA_CODENAME . "' " .
- FRIENDICA_VERSION . "-" .
- DB_UPDATE_VERSION . "; " .
- $this->get_baseurl();
- }
-
- function is_friendica_app() {
- return $this->is_friendica_app;
- }
-
- /**
- * @brief Checks if the site is called via a backend process
- *
- * This isn't a perfect solution. But we need this check very early.
- * So we cannot wait until the modules are loaded.
- *
- * @return bool Is it a known backend?
- */
- function is_backend() {
- static $backends = array();
- $backends[] = "_well_known";
- $backends[] = "api";
- $backends[] = "dfrn_notify";
- $backends[] = "fetch";
- $backends[] = "hcard";
- $backends[] = "hostxrd";
- $backends[] = "nodeinfo";
- $backends[] = "noscrape";
- $backends[] = "p";
- $backends[] = "poco";
- $backends[] = "post";
- $backends[] = "proxy";
- $backends[] = "pubsub";
- $backends[] = "pubsubhubbub";
- $backends[] = "receive";
- $backends[] = "rsd_xml";
- $backends[] = "salmon";
- $backends[] = "statistics_json";
- $backends[] = "xrd";
-
- // Check if current module is in backend or backend flag is set
- return (in_array($this->module, $backends) || $this->backend);
- }
-
- /**
- * @brief Checks if the maximum number of database processes is reached
- *
- * @return bool Is the limit reached?
- */
- function max_processes_reached() {
-
- if ($this->is_backend()) {
- $process = "backend";
- $max_processes = get_config('system', 'max_processes_backend');
- if (intval($max_processes) == 0) {
- $max_processes = 5;
- }
- } else {
- $process = "frontend";
- $max_processes = get_config('system', 'max_processes_frontend');
- if (intval($max_processes) == 0) {
- $max_processes = 20;
- }
- }
-
- $processlist = dbm::processlist();
- if ($processlist["list"] != "") {
- logger("Processcheck: Processes: ".$processlist["amount"]." - Processlist: ".$processlist["list"], LOGGER_DEBUG);
-
- if ($processlist["amount"] > $max_processes) {
- logger("Processcheck: Maximum number of processes for ".$process." tasks (".$max_processes.") reached.", LOGGER_DEBUG);
- return true;
- }
- }
- return false;
- }
-
- /**
- * @brief Checks if the maximum load is reached
- *
- * @return bool Is the load reached?
- */
- function maxload_reached() {
-
- if ($this->is_backend()) {
- $process = "backend";
- $maxsysload = intval(get_config('system', 'maxloadavg'));
- if ($maxsysload < 1) {
- $maxsysload = 50;
- }
- } else {
- $process = "frontend";
- $maxsysload = intval(get_config('system','maxloadavg_frontend'));
- if ($maxsysload < 1) {
- $maxsysload = 50;
- }
- }
-
- $load = current_load();
- if ($load) {
- if (intval($load) > $maxsysload) {
- logger('system: load '.$load.' for '.$process.' tasks ('.$maxsysload.') too high.');
- return true;
- }
- }
- return false;
- }
-
- function proc_run($args) {
-
- if (!function_exists("proc_open")) {
- return;
- }
-
- // 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.
- $cachekey = "app:proc_run:started";
- $result = Cache::get($cachekey);
-
- if (!is_null($result) AND (time() - $result) < 10) {
- return;
- }
-
- // Set the timestamp of the last proc_run
- Cache::set($cachekey, time(), CACHE_MINUTE);
-
- array_unshift($args, ((x($this->config,'php_path')) && (strlen($this->config['php_path'])) ? $this->config['php_path'] : 'php'));
-
- // add baseurl to args. cli scripts can't construct it
- $args[] = $this->get_baseurl();
-
- for ($x = 0; $x < count($args); $x ++) {
- $args[$x] = escapeshellarg($args[$x]);
- }
-
- $cmdline = implode($args, " ");
-
- if (get_config('system', 'proc_windows')) {
- $resource = proc_open('cmd /c start /b ' . $cmdline, array(), $foo, dirname(__FILE__));
- } else {
- $resource = proc_open($cmdline . " &", array(), $foo, dirname(__FILE__));
- }
- if (!is_resource($resource)) {
- logger('We got no resource for command '.$cmdline, LOGGER_DEBUG);
- return;
- }
- proc_close($resource);
- }
-
- /**
- * @brief Returns the system user that is executing the script
- *
- * This mostly returns something like "www-data".
- *
- * @return string system username
- */
- static function systemuser() {
- if (!function_exists('posix_getpwuid') OR !function_exists('posix_geteuid')) {
- return '';
- }
-
- $processUser = posix_getpwuid(posix_geteuid());
- return $processUser['name'];
- }
-
- /**
- * @brief Checks if a given directory is usable for the system
- *
- * @return boolean the directory is usable
- */
- static function directory_usable($directory) {
-
- if ($directory == '') {
- logger("Directory is empty. This shouldn't happen.", LOGGER_DEBUG);
- return false;
- }
-
- if (!file_exists($directory)) {
- logger('Path "'.$directory.'" does not exist for user '.self::systemuser(), LOGGER_DEBUG);
- return false;
- }
- if (is_file($directory)) {
- logger('Path "'.$directory.'" is a file for user '.self::systemuser(), LOGGER_DEBUG);
- return false;
- }
- if (!is_dir($directory)) {
- logger('Path "'.$directory.'" is not a directory for user '.self::systemuser(), LOGGER_DEBUG);
- return false;
- }
- if (!is_writable($directory)) {
- logger('Path "'.$directory.'" is not writable for user '.self::systemuser(), LOGGER_DEBUG);
- return false;
- }
- return true;
- }
}
/**
@@ -1436,7 +499,6 @@ function get_app() {
return $a;
}
-
/**
* @brief Multi-purpose function to check variable state.
*
@@ -1451,7 +513,7 @@ function get_app() {
*
* @return bool|int
*/
-function x($s,$k = NULL) {
+function x($s, $k = NULL) {
if ($k != NULL) {
if ((is_array($s)) && (array_key_exists($k, $s))) {
if ($s[$k]) {
@@ -1471,7 +533,6 @@ function x($s,$k = NULL) {
}
}
-
/**
* @brief Called from db initialisation if db is dead.
*/
@@ -1481,7 +542,6 @@ function system_unavailable() {
killme();
}
-
function clean_urls() {
$a = get_app();
return true;
@@ -1490,7 +550,7 @@ function clean_urls() {
function z_path() {
$base = App::get_baseurl();
- if (! clean_urls()) {
+ if (!clean_urls()) {
$base .= '/?q=';
}
@@ -1517,7 +577,7 @@ function z_root() {
* @return string
*/
function absurl($path) {
- if (strpos($path,'/') === 0) {
+ if (strpos($path, '/') === 0) {
return z_path() . $path;
}
return $path;
@@ -1534,25 +594,23 @@ function is_ajax() {
function check_db() {
- $build = get_config('system','build');
- if (! x($build)) {
- set_config('system','build',DB_UPDATE_VERSION);
+ $build = get_config('system', 'build');
+ if (!x($build)) {
+ set_config('system', 'build', DB_UPDATE_VERSION);
$build = DB_UPDATE_VERSION;
}
if ($build != DB_UPDATE_VERSION) {
proc_run(PRIORITY_CRITICAL, 'include/dbupdate.php');
}
-
}
-
/**
* Sets the base url for use in cmdline programs which don't have
* $_SERVER variables
*/
function check_url(App $a) {
- $url = get_config('system','url');
+ $url = get_config('system', 'url');
// if the url isn't set or the stored url is radically different
// than the currently visited url, store the current value accordingly.
@@ -1560,24 +618,23 @@ function check_url(App $a) {
// and www.example.com vs example.com.
// We will only change the url to an ip address if there is no existing setting
- if (! x($url)) {
- $url = set_config('system','url',App::get_baseurl());
+ if (!x($url)) {
+ $url = set_config('system', 'url', App::get_baseurl());
}
- if ((! link_compare($url,App::get_baseurl())) && (! preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/",$a->get_hostname))) {
- $url = set_config('system','url',App::get_baseurl());
+ if ((!link_compare($url, App::get_baseurl())) && (!preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $a->get_hostname))) {
+ $url = set_config('system', 'url', App::get_baseurl());
}
return;
}
-
/**
* @brief Automatic database updates
*/
function update_db(App $a) {
- $build = get_config('system','build');
- if (! x($build)) {
- $build = set_config('system','build',DB_UPDATE_VERSION);
+ $build = get_config('system', 'build');
+ if (!x($build)) {
+ $build = set_config('system', 'build', DB_UPDATE_VERSION);
}
if ($build != DB_UPDATE_VERSION) {
@@ -1588,7 +645,6 @@ function update_db(App $a) {
// We're reporting a different version than what is currently installed.
// Run any existing update scripts to bring the database up to current.
-
// make sure that boot.php and update.php are the same release, we might be
// updating right this very second and the correct version of the update.php
// file may not be here yet. This can happen on a very busy site.
@@ -1596,12 +652,12 @@ function update_db(App $a) {
if (DB_UPDATE_VERSION == UPDATE_VERSION) {
// Compare the current structure with the defined structure
- $t = get_config('database','dbupdate_'.DB_UPDATE_VERSION);
+ $t = get_config('database', 'dbupdate_' . DB_UPDATE_VERSION);
if ($t !== false) {
return;
}
- set_config('database','dbupdate_'.DB_UPDATE_VERSION, time());
+ set_config('database', 'dbupdate_' . DB_UPDATE_VERSION, time());
// run old update routine (wich could modify the schema and
// conflits with new routine)
@@ -1625,7 +681,7 @@ function update_db(App $a) {
);
return;
} else {
- set_config('database','dbupdate_'.DB_UPDATE_VERSION, 'success');
+ set_config('database', 'dbupdate_' . DB_UPDATE_VERSION, 'success');
}
// run any left update_nnnn functions in update.php
@@ -1649,15 +705,14 @@ function run_update_function($x) {
// We want exactly one process to run the update command.
// So store the fact that we're taking responsibility
// after first checking to see if somebody else already has.
-
// If the update fails or times-out completely you may need to
// delete the config entry to try again.
- $t = get_config('database','update_' . $x);
+ $t = get_config('database', 'update_' . $x);
if ($t !== false) {
return false;
}
- set_config('database','update_' . $x, time());
+ set_config('database', 'update_' . $x, time());
// call the specific update
@@ -1672,13 +727,13 @@ function run_update_function($x) {
);
return false;
} else {
- set_config('database','update_' . $x, 'success');
- set_config('system','build', $x + 1);
+ set_config('database', 'update_' . $x, 'success');
+ set_config('system', 'build', $x + 1);
return true;
}
} else {
- set_config('database','update_' . $x, 'success');
- set_config('system','build', $x + 1);
+ set_config('database', 'update_' . $x, 'success');
+ set_config('system', 'build', $x + 1);
return true;
}
return true;
@@ -1707,11 +762,11 @@ function check_plugins(App $a) {
$installed = array();
}
- $plugins = get_config('system','addon');
+ $plugins = get_config('system', 'addon');
$plugins_arr = array();
if ($plugins) {
- $plugins_arr = explode(',',str_replace(' ', '',$plugins));
+ $plugins_arr = explode(',', str_replace(' ', '', $plugins));
}
$a->plugins = $plugins_arr;
@@ -1720,7 +775,7 @@ function check_plugins(App $a) {
if (count($installed)) {
foreach ($installed as $i) {
- if (! in_array($i['name'],$plugins_arr)) {
+ if (!in_array($i['name'], $plugins_arr)) {
uninstall_plugin($i['name']);
} else {
$installed_arr[] = $i['name'];
@@ -1730,20 +785,18 @@ function check_plugins(App $a) {
if (count($plugins_arr)) {
foreach ($plugins_arr as $p) {
- if (! in_array($p,$installed_arr)) {
+ if (!in_array($p, $installed_arr)) {
install_plugin($p);
}
}
}
-
load_hooks();
return;
}
-function get_guid($size=16, $prefix = "") {
-
+function get_guid($size = 16, $prefix = "") {
if ($prefix == "") {
$a = get_app();
$prefix = hash("crc32", $a->get_hostname());
@@ -1776,7 +829,7 @@ function get_guid($size=16, $prefix = "") {
* @hooks 'login_hook'
* string $o
*/
-function login($register = false, $hiddens=false) {
+function login($register = false, $hiddens = false) {
$a = get_app();
$o = "";
$reg = false;
@@ -1787,14 +840,14 @@ function login($register = false, $hiddens=false) {
);
}
- $noid = get_config('system','no_openid');
+ $noid = get_config('system', 'no_openid');
$dest_url = $a->query_string;
if (local_user()) {
$tpl = get_markup_template("logout.tpl");
} else {
- $a->page['htmlhead'] .= replace_macros(get_markup_template("login_head.tpl"),array(
+ $a->page['htmlhead'] .= replace_macros(get_markup_template("login_head.tpl"), array(
'$baseurl' => $a->get_baseurl(true)
));
@@ -1828,10 +881,9 @@ function login($register = false, $hiddens=false) {
'$privacytitle' => t('Website Privacy Policy'),
'$privacylink' => t('privacy policy'),
-
));
- call_hooks('login_hook',$o);
+ call_hooks('login_hook', $o);
return $o;
}
@@ -1840,7 +892,6 @@ function login($register = false, $hiddens=false) {
* @brief Used to end the current process, after saving session state.
*/
function killme() {
-
if (!get_app()->is_backend()) {
session_write_close();
}
@@ -1853,14 +904,13 @@ function killme() {
*/
function goaway($s) {
if (!strstr(normalise_link($s), "http://")) {
- $s = App::get_baseurl()."/".$s;
+ $s = App::get_baseurl() . "/" . $s;
}
header("Location: $s");
killme();
}
-
/**
* @brief Returns the user id of locally logged in user or false.
*
@@ -1902,7 +952,7 @@ function public_contact() {
* @return int|bool visitor_id or false
*/
function remote_user() {
- if ((x($_SESSION,'authenticated')) && (x($_SESSION,'visitor_id'))) {
+ if ((x($_SESSION, 'authenticated')) && (x($_SESSION, 'visitor_id'))) {
return intval($_SESSION['visitor_id']);
}
return false;
@@ -1917,7 +967,7 @@ function remote_user() {
*/
function notice($s) {
$a = get_app();
- if (! x($_SESSION,'sysmsg')) {
+ if (!x($_SESSION, 'sysmsg')) {
$_SESSION['sysmsg'] = array();
}
if ($a->interactive) {
@@ -1935,11 +985,11 @@ function notice($s) {
function info($s) {
$a = get_app();
- if (local_user() AND get_pconfig(local_user(),'system','ignore_info')) {
+ if (local_user() AND get_pconfig(local_user(), 'system', 'ignore_info')) {
return;
}
- if (! x($_SESSION,'sysmsg_info')) {
+ if (!x($_SESSION, 'sysmsg_info')) {
$_SESSION['sysmsg_info'] = array();
}
if ($a->interactive) {
@@ -1947,7 +997,6 @@ function info($s) {
}
}
-
/**
* @brief Wrapper around config to limit the text length of an incoming message
*
@@ -1955,12 +1004,12 @@ function info($s) {
*/
function get_max_import_size() {
$a = get_app();
- return ((x($a->config,'max_import_size')) ? $a->config['max_import_size'] : 0 );
+ return ((x($a->config, 'max_import_size')) ? $a->config['max_import_size'] : 0 );
}
/**
* @brief Wrap calls to proc_close(proc_open()) and call hook
- * so plugins can take part in process :)
+ * so plugins can take part in process :)
*
* @param (integer|array) priority or parameter array, $cmd atrings are deprecated and are ignored
*
@@ -1971,9 +1020,9 @@ function get_max_import_size() {
* @note $cmd and string args are surrounded with ""
*
* @hooks 'proc_run'
- * array $arr
+ * array $arr
*/
-function proc_run($cmd){
+function proc_run($cmd) {
$a = get_app();
@@ -2006,7 +1055,7 @@ function proc_run($cmd){
$arr = array('args' => $args, 'run_cmd' => true);
call_hooks("proc_run", $arr);
- if (!$arr['run_cmd'] OR !count($args)) {
+ if (!$arr['run_cmd'] OR ! count($args)) {
return;
}
@@ -2028,8 +1077,7 @@ function proc_run($cmd){
array_shift($argv);
$parameters = json_encode($argv);
- $found = q("SELECT `id` FROM `workerqueue` WHERE `parameter` = '%s'",
- dbesc($parameters));
+ $found = q("SELECT `id` FROM `workerqueue` WHERE `parameter` = '%s'", dbesc($parameters));
if (!dbm::is_result($found)) {
q("INSERT INTO `workerqueue` (`parameter`, `created`, `priority`)
@@ -2065,7 +1113,7 @@ function proc_run($cmd){
$a->proc_run($args);
}
-function current_theme(){
+function current_theme() {
$app_base_themes = array('duepuntozero', 'dispy', 'quattro');
$a = get_app();
@@ -2087,7 +1135,7 @@ function current_theme(){
// This works only if the user is on the same server
if ($page_theme && local_user() && (local_user() != $a->profile_uid)) {
- if (get_pconfig(local_user(),'system','always_my_theme')) {
+ if (get_pconfig(local_user(), 'system', 'always_my_theme')) {
$page_theme = null;
}
}
@@ -2097,7 +1145,7 @@ function current_theme(){
$is_mobile = $a->is_mobile || $a->is_tablet;
$standard_system_theme = Config::get('system', 'theme', '');
- $standard_theme_name = ((isset($_SESSION) && x($_SESSION,'theme')) ? $_SESSION['theme'] : $standard_system_theme);
+ $standard_theme_name = ((isset($_SESSION) && x($_SESSION, 'theme')) ? $_SESSION['theme'] : $standard_system_theme);
if ($is_mobile) {
if (isset($_SESSION['show-mobile']) && !$_SESSION['show-mobile']) {
@@ -2108,7 +1156,7 @@ function current_theme(){
if ($system_theme == '') {
$system_theme = $standard_system_theme;
}
- $theme_name = ((isset($_SESSION) && x($_SESSION,'mobile-theme')) ? $_SESSION['mobile-theme'] : $system_theme);
+ $theme_name = ((isset($_SESSION) && x($_SESSION, 'mobile-theme')) ? $_SESSION['mobile-theme'] : $system_theme);
if ($theme_name === '---') {
// user has selected to have the mobile theme be the same as the normal one
@@ -2130,21 +1178,21 @@ function current_theme(){
}
if ($theme_name &&
- (file_exists('view/theme/' . $theme_name . '/style.css') ||
- file_exists('view/theme/' . $theme_name . '/style.php'))) {
+ (file_exists('view/theme/' . $theme_name . '/style.css') ||
+ file_exists('view/theme/' . $theme_name . '/style.php'))) {
return($theme_name);
}
foreach ($app_base_themes as $t) {
if (file_exists('view/theme/' . $t . '/style.css') ||
- file_exists('view/theme/' . $t . '/style.php')) {
+ file_exists('view/theme/' . $t . '/style.php')) {
return($t);
}
}
- $fallback = array_merge(glob('view/theme/*/style.css'),glob('view/theme/*/style.php'));
+ $fallback = array_merge(glob('view/theme/*/style.css'), glob('view/theme/*/style.php'));
if (count($fallback)) {
- return (str_replace('view/theme/','', substr($fallback[0],0,-10)));
+ return (str_replace('view/theme/', '', substr($fallback[0], 0, -10)));
}
/// @TODO No final return statement?
@@ -2164,13 +1212,13 @@ function current_theme_url() {
$opts = (($a->profile_uid) ? '?f=&puid=' . $a->profile_uid : '');
if (file_exists('view/theme/' . $t . '/style.php')) {
- return('view/theme/'.$t.'/style.pcss'.$opts);
+ return('view/theme/' . $t . '/style.pcss' . $opts);
}
- return('view/theme/'.$t.'/style.css');
+ return('view/theme/' . $t . '/style.css');
}
-function feed_birthday($uid,$tz) {
+function feed_birthday($uid, $tz) {
/**
*
@@ -2190,11 +1238,9 @@ function feed_birthday($uid,$tz) {
* 6:00PM the day before, but that will correspond to midnight to the birthday person.
*
*/
-
-
$birthday = '';
- if (! strlen($tz)) {
+ if (!strlen($tz)) {
$tz = 'UTC';
}
@@ -2203,16 +1249,16 @@ function feed_birthday($uid,$tz) {
);
if (dbm::is_result($p)) {
- $tmp_dob = substr($p[0]['dob'],5);
+ $tmp_dob = substr($p[0]['dob'], 5);
if (intval($tmp_dob)) {
- $y = datetime_convert($tz,$tz,'now','Y');
+ $y = datetime_convert($tz, $tz, 'now', 'Y');
$bd = $y . '-' . $tmp_dob . ' 00:00';
$t_dob = strtotime($bd);
- $now = strtotime(datetime_convert($tz,$tz,'now'));
+ $now = strtotime(datetime_convert($tz, $tz, 'now'));
if ($t_dob < $now) {
$bd = $y + 1 . '-' . $tmp_dob . ' 00:00';
}
- $birthday = datetime_convert($tz,'UTC',$bd,ATOM_TIME);
+ $birthday = datetime_convert($tz, 'UTC', $bd, ATOM_TIME);
}
}
@@ -2230,7 +1276,7 @@ function is_site_admin() {
$adminlist = explode(",", str_replace(" ", "", $a->config['admin_email']));
//if(local_user() && x($a->user,'email') && x($a->config,'admin_email') && ($a->user['email'] === $a->config['admin_email']))
- if (local_user() && x($a->user,'email') && x($a->config,'admin_email') && in_array($a->user['email'], $adminlist)) {
+ if (local_user() && x($a->user, 'email') && x($a->config, 'admin_email') && in_array($a->user['email'], $adminlist)) {
return true;
}
return false;
@@ -2252,7 +1298,7 @@ function build_querystring($params, $name = null) {
if ($name == null) {
$ret .= build_querystring($val, $key);
} else {
- $ret .= build_querystring($val, $name."[$key]");
+ $ret .= build_querystring($val, $name . "[$key]");
}
} else {
$val = urlencode($val);
@@ -2300,10 +1346,10 @@ function explode_querystring($query) {
}
/**
-* Returns the complete URL of the current page, e.g.: http(s)://something.com/network
-*
-* Taken from http://webcheatsheet.com/php/get_current_page_url.php
-*/
+ * Returns the complete URL of the current page, e.g.: http(s)://something.com/network
+ *
+ * Taken from http://webcheatsheet.com/php/get_current_page_url.php
+ */
function curPageURL() {
$pageURL = 'http';
if ($_SERVER["HTTPS"] == "on") {
@@ -2313,9 +1359,9 @@ function curPageURL() {
$pageURL .= "://";
if ($_SERVER["SERVER_PORT"] != "80" && $_SERVER["SERVER_PORT"] != "443") {
- $pageURL .= $_SERVER["SERVER_NAME"].":".$_SERVER["SERVER_PORT"].$_SERVER["REQUEST_URI"];
+ $pageURL .= $_SERVER["SERVER_NAME"] . ":" . $_SERVER["SERVER_PORT"] . $_SERVER["REQUEST_URI"];
} else {
- $pageURL .= $_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
+ $pageURL .= $_SERVER["SERVER_NAME"] . $_SERVER["REQUEST_URI"];
}
return $pageURL;
}
@@ -2324,7 +1370,7 @@ function random_digits($digits) {
$rn = '';
for ($i = 0; $i < $digits; $i++) {
/// @TODO rand() is different to mt_rand() and maybe lesser "random"
- $rn .= rand(0,9);
+ $rn .= rand(0, 9);
}
return $rn;
}
@@ -2342,7 +1388,7 @@ function get_server() {
function get_cachefile($file, $writemode = true) {
$cache = get_itemcachepath();
- if ((! $cache) || (! is_dir($cache))) {
+ if ((!$cache) || (!is_dir($cache))) {
return("");
}
@@ -2375,15 +1421,15 @@ function clear_cache($basepath = "", $path = "") {
return;
}
- $cachetime = (int)get_config('system','itemcache_duration');
+ $cachetime = (int) get_config('system', 'itemcache_duration');
if ($cachetime == 0) {
$cachetime = 86400;
}
- if (is_writable($path)){
+ if (is_writable($path)) {
if ($dh = opendir($path)) {
while (($file = readdir($dh)) !== false) {
- $fullpath = $path."/".$file;
+ $fullpath = $path . "/" . $file;
if ((filetype($fullpath) == "dir") and ($file != ".") and ($file != "..")) {
clear_cache($basepath, $fullpath);
}
@@ -2398,12 +1444,12 @@ function clear_cache($basepath = "", $path = "") {
function get_itemcachepath() {
// Checking, if the cache is deactivated
- $cachetime = (int)get_config('system','itemcache_duration');
+ $cachetime = (int) get_config('system', 'itemcache_duration');
if ($cachetime < 0) {
return "";
}
- $itemcache = get_config('system','itemcache');
+ $itemcache = get_config('system', 'itemcache');
if (($itemcache != "") AND App::directory_usable($itemcache)) {
return $itemcache;
}
@@ -2411,7 +1457,7 @@ function get_itemcachepath() {
$temppath = get_temppath();
if ($temppath != "") {
- $itemcache = $temppath."/itemcache";
+ $itemcache = $temppath . "/itemcache";
if (!file_exists($itemcache) && !is_dir($itemcache)) {
mkdir($itemcache);
}
@@ -2430,7 +1476,7 @@ function get_itemcachepath() {
* @return string Spool path
*/
function get_spoolpath() {
- $spoolpath = get_config('system','spoolpath');
+ $spoolpath = get_config('system', 'spoolpath');
if (($spoolpath != "") AND App::directory_usable($spoolpath)) {
// We have a spool path and it is usable
return $spoolpath;
@@ -2441,7 +1487,7 @@ function get_spoolpath() {
if ($temppath != "") {
// To avoid any interferences with other systems we create our own directory
- $spoolpath = $temppath."/spool";
+ $spoolpath = $temppath . "/spool";
if (!is_dir($spoolpath)) {
mkdir($spoolpath);
}
@@ -2477,7 +1523,7 @@ function get_temppath() {
// Check if it is usable
if (($temppath != "") AND App::directory_usable($temppath)) {
// To avoid any interferences with other systems we create our own directory
- $new_temppath = $temppath."/".$a->get_hostname();
+ $new_temppath = $temppath . "/" . $a->get_hostname();
if (!is_dir($new_temppath)) {
/// @TODO There is a mkdir()+chmod() upwards, maybe generalize this (+ configurable) into a function/method?
mkdir($new_temppath);
@@ -2523,7 +1569,7 @@ function validate_include(&$file) {
return false;
}
- $file = str_replace(getcwd()."/", "", $file, $count);
+ $file = str_replace(getcwd() . "/", "", $file, $count);
if ($count != 1) {
return false;
}
@@ -2575,7 +1621,7 @@ function argc() {
* @return string Value of the argv key
*/
function argv($x) {
- if (array_key_exists($x,get_app()->argv)) {
+ if (array_key_exists($x, get_app()->argv)) {
return get_app()->argv[$x];
}
@@ -2592,12 +1638,12 @@ function argv($x) {
*
* @param string $module The name of the module (e.g. "network")
* @return array Of infinite scroll data
- * 'pageno' => $pageno The number of the actual page
- * 'reload_uri' => $reload_uri The URI of the content we have to load
+ * 'pageno' => $pageno The number of the actual page
+ * 'reload_uri' => $reload_uri The URI of the content we have to load
*/
function infinite_scroll_data($module) {
- if (get_pconfig(local_user(),'system','infinite_scroll')
+ if (get_pconfig(local_user(), 'system', 'infinite_scroll')
AND ($module == "network") AND ($_GET["mode"] != "minimal")) {
// get the page number
@@ -2616,7 +1662,7 @@ function infinite_scroll_data($module) {
}
}
- if (($a->page_offset != "") AND !strstr($reload_uri, "&offset=")) {
+ if (($a->page_offset != "") AND ! strstr($reload_uri, "&offset=")) {
$reload_uri .= "&offset=" . urlencode($a->page_offset);
}
diff --git a/composer.lock b/composer.lock
index b46614f6e..f057d0cec 100644
--- a/composer.lock
+++ b/composer.lock
@@ -226,10 +226,10 @@
},
{
"name": "pear-pear.php.net/PEAR",
- "version": "1.10.3",
+ "version": "1.10.4",
"dist": {
"type": "file",
- "url": "https://pear.php.net/get/PEAR-1.10.3.tgz",
+ "url": "https://pear.php.net/get/PEAR-1.10.4.tgz",
"reference": null,
"shasum": null
},
@@ -247,7 +247,7 @@
"pear-pear.php.net/pear_frontend_web": "<=0.4.0.0"
},
"replace": {
- "pear-pear/pear": "== 1.10.3.0"
+ "pear-pear/pear": "== 1.10.4.0"
},
"type": "pear-library",
"autoload": {
diff --git a/database.sql b/database.sql
index 69da511b0..73547b305 100644
--- a/database.sql
+++ b/database.sql
@@ -1,6 +1,6 @@
-- ------------------------------------------
-- Friendica 3.5.2-dev (Asparagus)
--- DB_UPDATE_VERSION 1219
+-- DB_UPDATE_VERSION 1221
-- ------------------------------------------
@@ -174,13 +174,13 @@ CREATE TABLE IF NOT EXISTS `contact` (
`fetch_further_information` tinyint(1) NOT NULL DEFAULT 0,
`ffi_keyword_blacklist` text,
PRIMARY KEY(`id`),
- INDEX `uid_name` (`uid`,`name`),
+ INDEX `uid_name` (`uid`,`name`(190)),
INDEX `self_uid` (`self`,`uid`),
INDEX `alias_uid` (`alias`(32),`uid`),
INDEX `pending_uid` (`pending`,`uid`),
INDEX `blocked_uid` (`blocked`,`uid`),
- INDEX `uid_rel_network_poll` (`uid`,`rel`,`network`,`poll`(64),`archive`),
- INDEX `uid_network_batch` (`uid`,`network`,`batch`(64)),
+ INDEX `uid_rel_network_poll` (`uid`,`rel`,`network`(4),`poll`(64),`archive`),
+ INDEX `uid_network_batch` (`uid`,`network`(4),`batch`(64)),
INDEX `addr_uid` (`addr`(32),`uid`),
INDEX `nurl_uid` (`nurl`(32),`uid`),
INDEX `nick_uid` (`nick`(32),`uid`),
@@ -204,6 +204,21 @@ CREATE TABLE IF NOT EXISTS `conv` (
INDEX `uid` (`uid`)
) DEFAULT COLLATE utf8mb4_general_ci;
+--
+-- TABLE conversation
+--
+CREATE TABLE IF NOT EXISTS `conversation` (
+ `item-uri` varbinary(255) NOT NULL,
+ `reply-to-uri` varbinary(255) NOT NULL DEFAULT '',
+ `conversation-uri` varbinary(255) NOT NULL DEFAULT '',
+ `conversation-href` varbinary(255) NOT NULL DEFAULT '',
+ `protocol` tinyint(1) unsigned NOT NULL DEFAULT 0,
+ `source` mediumtext,
+ `received` datetime NOT NULL DEFAULT '0001-01-01 00:00:00',
+ PRIMARY KEY(`item-uri`),
+ INDEX `conversation-uri` (`conversation-uri`)
+) DEFAULT COLLATE utf8mb4_general_ci;
+
--
-- TABLE event
--
@@ -344,7 +359,7 @@ CREATE TABLE IF NOT EXISTS `gcontact` (
INDEX `name` (`name`(64)),
INDEX `nick` (`nick`(32)),
INDEX `addr` (`addr`(64)),
- INDEX `hide_network_updated` (`hide`,`network`,`updated`),
+ INDEX `hide_network_updated` (`hide`,`network`(4),`updated`),
INDEX `updated` (`updated`)
) DEFAULT COLLATE utf8mb4_general_ci;
@@ -523,22 +538,22 @@ CREATE TABLE IF NOT EXISTS `item` (
INDEX `uid_contactid_id` (`uid`,`contact-id`,`id`),
INDEX `uid_created` (`uid`,`created`),
INDEX `uid_unseen_contactid` (`uid`,`unseen`,`contact-id`),
- INDEX `uid_network_received` (`uid`,`network`,`received`),
- INDEX `uid_network_commented` (`uid`,`network`,`commented`),
- INDEX `uid_thrparent` (`uid`,`thr-parent`),
- INDEX `uid_parenturi` (`uid`,`parent-uri`),
+ INDEX `uid_network_received` (`uid`,`network`(4),`received`),
+ INDEX `uid_network_commented` (`uid`,`network`(4),`commented`),
+ INDEX `uid_thrparent` (`uid`,`thr-parent`(190)),
+ INDEX `uid_parenturi` (`uid`,`parent-uri`(190)),
INDEX `uid_contactid_created` (`uid`,`contact-id`,`created`),
INDEX `authorid_created` (`author-id`,`created`),
- INDEX `uid_uri` (`uid`,`uri`),
+ INDEX `uid_uri` (`uid`,`uri`(190)),
INDEX `resource-id` (`resource-id`),
INDEX `contactid_allowcid_allowpid_denycid_denygid` (`contact-id`,`allow_cid`(10),`allow_gid`(10),`deny_cid`(10),`deny_gid`(10)),
- INDEX `uid_type_changed` (`uid`,`type`,`changed`),
- INDEX `contactid_verb` (`contact-id`,`verb`),
+ INDEX `uid_type_changed` (`uid`,`type`(190),`changed`),
+ INDEX `contactid_verb` (`contact-id`,`verb`(190)),
INDEX `deleted_changed` (`deleted`,`changed`),
INDEX `uid_wall_changed` (`uid`,`wall`,`changed`),
INDEX `uid_eventid` (`uid`,`event-id`),
- INDEX `uid_authorlink` (`uid`,`author-link`),
- INDEX `uid_ownerlink` (`uid`,`owner-link`)
+ INDEX `uid_authorlink` (`uid`,`author-link`(190)),
+ INDEX `uid_ownerlink` (`uid`,`owner-link`(190))
) DEFAULT COLLATE utf8mb4_general_ci;
--
@@ -652,7 +667,7 @@ CREATE TABLE IF NOT EXISTS `notify` (
INDEX `hash_uid` (`hash`,`uid`),
INDEX `seen_uid_date` (`seen`,`uid`,`date`),
INDEX `uid_date` (`uid`,`date`),
- INDEX `uid_type_link` (`uid`,`type`,`link`)
+ INDEX `uid_type_link` (`uid`,`type`,`link`(190))
) DEFAULT COLLATE utf8mb4_general_ci;
--
@@ -963,7 +978,7 @@ CREATE TABLE IF NOT EXISTS `term` (
`aid` int(10) unsigned NOT NULL DEFAULT 0,
`uid` int(10) unsigned NOT NULL DEFAULT 0,
PRIMARY KEY(`tid`),
- INDEX `oid_otype_type_term` (`oid`,`otype`,`type`,`term`),
+ INDEX `oid_otype_type_term` (`oid`,`otype`,`type`,`term`(32)),
INDEX `uid_otype_type_term_global_created` (`uid`,`otype`,`type`,`term`(32),`global`,`created`),
INDEX `uid_otype_type_url` (`uid`,`otype`,`type`,`url`(64)),
INDEX `guid` (`guid`(64))
diff --git a/doc/Composer.md b/doc/Composer.md
index 8310baf3f..02ce61639 100644
--- a/doc/Composer.md
+++ b/doc/Composer.md
@@ -113,3 +113,8 @@ For Composer, this would be:
````
$> COMPOSER_HOME=/var/tmp/composer sudo -u [web user] util/composer.phar [mode]
````
+
+## Related
+
+* [Class autoloading](help/autoloader)
+* [How To Move Classes to `src`](help/Developer-How-To-Move-Classes-to-src)
diff --git a/doc/Developer-How-To-Move-Classes-to-src.md b/doc/Developer-How-To-Move-Classes-to-src.md
new file mode 100644
index 000000000..25eb39372
--- /dev/null
+++ b/doc/Developer-How-To-Move-Classes-to-src.md
@@ -0,0 +1,108 @@
+How To Move Classes to `src`
+==============
+
+* [Home](help)
+ * [Developer Intro](help/Developers-Intro)
+
+Friendica uses [Composer](help/Composer) to manage autoloading.
+This means that all the PHP class files moved to the `src` folder will be [automatically included](help/autoloader) when the class it defines is first used in the flow.
+This is an improvement over the current `require` usage since files will be included on an actual usage basis instead of the presence of a `require` call.
+
+However, there are a significant number of items to check when moving a class file from the `include` folder to the `src` folder, and this page is there to list them.
+
+## Decide the namespace
+
+This isn't the most technical decision of them all, but it has long lasting consequences as it will be the name that will be used to refer to this class from now on.
+There is [a shared Ethercalc sheet](https://ethercalc.org/friendica_classes) to suggest namespace/class names that lists all the already moved class files for inspiration.
+
+A few pointers though:
+* `Friendica` is the base namespace for all classes in the `src` folder
+* Namespaces match the directory structure, with `Friendica` namespace being the base `src` directory. The `Config` class set in the `Friendica\Core` namespace is expected to be found at `src/Core/Config.php`.
+* Namespaces can help group classes with a similar purpose or relevant to a particular feature
+
+When you're done deciding the namespace, it's time to use it.
+Let's say we choose `Friendica\Core` for the `Config` class.
+
+## Use the namespace
+
+To declare the namespace, the file `src/Core/Config.php` must start with the following statement:
+
+````php
+namespace Friendica\Core;
+````
+
+From now on, the `Config` class can be referred to as `Friendica\Core\Config`, however it isn't very practical, especially when the class was previously used as `Config`.
+Thankfully, PHP provides namespace shortcuts through `use`.
+
+This language construct just provides a different naming scheme for a namespace or a class, but doesn't trigger the autoload mechanism on its own.
+Here are the different ways you can use `use`:
+
+````php
+// No use
+$config = new Friendica\Core\Config();
+````
+````php
+// Namespace shortcut
+use Friendica\Core;
+
+$config = new Core\Config();
+````
+````php
+// Class name shortcut
+use Friendica\Core\Config;
+
+$config = new Config();
+````
+````php
+// Aliasing
+use Friendica\Core\Config as Cfg;
+
+$config = new Cfg();
+````
+
+Whatever the style chosen, a repository-wide search has to be done to find all the class name usage and either use the fully-qualified class name (including the namespace) or add a `use` statement at the start of each relevant file.
+
+## Escape non-namespace classes
+
+The class file you just moved is now in the `Friendica` namespace, but it probably isn't the case for all the classes referenced in this file.
+Since we added a `namespace Friendica\Core;` to the file, all the class names still declared in `include` will be implicitly understood as `Friendica\Core\ClassName`, which is rarely what we expect.
+
+To avoid `Class Friendica\Core\ClassName not found` errors, all the `include`-declared class names have to be prepended with a `\`, it tells the autoloader not to look for the class in the namespace but in the global space where non-namespaced classes are set.
+If there are only a handful of references to a single non-namespaced class, just prepending `\` is enough. However, if there are many instance, we can use `use` again.
+
+````php
+namespace Friendica\Core;
+...
+if (\dbm::is_result($r)) {
+ ...
+}
+````
+````php
+namespace Friendica\Core;
+
+use \dbm;
+
+if (dbm::is_result($r)) {
+ ...
+}
+````
+
+## Remove any useless `require`
+
+Now that you successfully moved your class to the autoloaded `src` folder, there's no need to include this file anywhere in the app ever again.
+Please remove all the `require_once` mentions of the former file, as they will provoke a Fatal Error even if the class isn't used.
+
+## Miscellaneous tips
+
+When you are done with moving the class, please run `php util/typo.php` from the Friendica base directory to check for obvious mistakes.
+Howevever, this tool isn't bullet-proof, and a staging install of Friendica is recommended to test your class move without impairing your production server if you host one.
+
+Most of Friendica processes are run in the background, so make sure to turn on your debug log to check for errors that wouldn't show up while simply browsing Friendica.
+
+Check the class file for any magic constant `__FILE__` or `__DIR__`, as their value changed since you moved the class in the file tree.
+Most of the time it's used for debugging purposes but there can be instances where it's used to create cache folders for example.
+
+## Related
+
+* [Class autoloading](help/autoloader)
+* [Using Composer](help/Composer)
\ No newline at end of file
diff --git a/doc/Developers-Intro.md b/doc/Developers-Intro.md
index 5568afeb5..3cda8ab46 100644
--- a/doc/Developers-Intro.md
+++ b/doc/Developers-Intro.md
@@ -52,7 +52,9 @@ Friendica uses [Composer](https://getcomposer.org) to manage dependencies librar
It's a command-line tool that downloads required libraries into the `vendor` folder and makes any namespaced class in `src` available through the whole application through `boot.php`.
+* [Class autoloading](help/autoloader)
* [Using Composer](help/Composer)
+* [How To Move Classes to `src`](help/Developer-How-To-Move-Classes-to-src)
###Coding standards
diff --git a/doc/Home.md b/doc/Home.md
index 5490d171f..4cecec44e 100644
--- a/doc/Home.md
+++ b/doc/Home.md
@@ -36,21 +36,25 @@ Friendica Documentation and Resources
**Developer Manual**
-* [Where to get started?](help/Developers-Intro)
-* [Help on Github](help/Github)
-* [Help on Vagrant](help/Vagrant)
-* [How to translate Friendica](help/translations)
-* [Bugs and Issues](help/Bugs-and-Issues)
-* [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)
-* [Using Composer](help/Composer)
-* [Code - Reference(Doxygen generated - sets cookies)](doc/html/)
-* [Twitter/GNU Social API Functions](help/api)
-
+* [Get started](help/Developers-Intro)
+* Set up development environment
+ * [Help on Github](help/Github)
+ * [Help on Vagrant](help/Vagrant)
+ * [Bugs and Issues](help/Bugs-and-Issues)
+* Code structure
+ * [Plugin Development](help/Plugins)
+ * [Theme Development](help/themes)
+ * [Smarty 3 Templates](help/smarty3-templates)
+* How To
+ * [Translate Friendica](help/translations)
+ * [Use Composer](help/Composer)
+ * [Move classes to `src`](help/Developer-How-To-Move-Classes-to-src)
+* Reference
+ * [Twitter/GNU Social API Functions](help/api)
+ * [Code (Doxygen generated - sets cookies)](doc/html/)
+ * [Protocol Documentation](help/Protocol)
+ * [Database schema documantation](help/database)
+ * [Class Autoloading](help/autoloader)
**External Resources**
diff --git a/doc/Install.md b/doc/Install.md
index b817339bb..a8ea2c050 100644
--- a/doc/Install.md
+++ b/doc/Install.md
@@ -28,7 +28,7 @@ Requirements
* Apache with mod-rewrite enabled and "Options All" so you can use a local .htaccess file
* PHP 5.4+.
* PHP *command line* access with register_argc_argv set to true in the php.ini file
-* curl, gd, mysql, hash and openssl extensions
+* Curl, GD, PDO, MySQLi, hash and OpenSSL extensions
* some form of email server or email gateway such that PHP mail() works
* Mysql 5.5.3+ or an equivalant alternative for MySQL (MariaDB, Percona Server etc.)
* the ability to schedule jobs with cron (Linux/Mac) or Scheduled Tasks (Windows) (Note: other options are presented in Section 7 of this document.)
diff --git a/doc/Settings.md b/doc/Settings.md
index 13424a5a4..e84418a45 100644
--- a/doc/Settings.md
+++ b/doc/Settings.md
@@ -242,6 +242,12 @@ The receiving end might be off-line, there might be a high system load and so on
Don't panic!
Friendica will not queue messages for all time but will sort out *dead* nodes automatically after a while and remove messages from the queue then.
+## Server Blocklist
+
+This page allows to block all communications (inbound and outbound) with a specific domain name.
+Each blocked domain entry requires a reason that will be displayed on the [friendica](/friendica) page.
+Matching is exact, blocking a domain doesn't block subdomains.
+
## Federation Statistics
The federation statistics page gives you a short summery of the nodes/servers/pods of the decentralized social network federation your node knows.
diff --git a/doc/api.md b/doc/api.md
index b759b4697..04abaa563 100644
--- a/doc/api.md
+++ b/doc/api.md
@@ -1,885 +1,1004 @@
-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.
-
-## Implemented API calls
-
-### General
-#### HTTP Method
-
-API endpoints can restrict the method used to request them.
-Using an invalid method results in HTTP error 405 "Method Not Allowed".
-
-In this document, the required method is listed after the endpoint name. "*" means every method can be used.
-
-#### Auth
-
-Friendica supports basic http auth and OAuth 1 to authenticate the user to the api.
-
-OAuth settings can be added by the user in web UI under /settings/oauth/
-
-In this document, endpoints which requires auth are marked with "AUTH" after endpoint name
-
-#### Unsupported parameters
-* cursor: Not implemented in GNU Social
-* trim_user: Not implemented in GNU Social
-* contributor_details: Not implemented in GNU Social
-* place_id: Not implemented in GNU Social
-* display_coordinates: Not implemented in GNU Social
-* include_rts: To-Do
-* include_my_retweet: Retweets in Friendica are implemented in a different way
-
-#### Different behaviour
-* screen_name: The nick name in friendica is only unique in each network but not for all networks. The users are searched in the following priority: Friendica, StatusNet/GNU Social, Diaspora, pump.io, Twitter. If no contact was found by this way, then the first contact is taken.
-* include_entities: Default is "false". If set to "true" then the plain text is formatted so that links are having descriptions.
-
-#### Return values
-* cid: Contact id of the user (important for "contact_allow" and "contact_deny")
-* network: network of the user
-
-#### Errors
-When an error occour in API call, an HTTP error code is returned, with an error message
-Usually:
-- 400 Bad Request: if parameter are missing or items can't be found
-- 403 Forbidden: if authenticated user is missing
-- 405 Method Not Allowed: if API was called with invalid method, eg. GET when API require POST
-- 501 Not Implemented: if requested API doesn't exists
-- 500 Internal Server Error: on other error contitions
-
-Error body is
-
-json:
-```
- {
- "error": "Specific error message",
- "request": "API path requested",
- "code": "HTTP error code"
- }
-```
-
-xml:
-```
-
- Specific error message
- API path requested
- HTTP error code
-
-```
-
----
-### account/rate_limit_status (*; AUTH)
-
----
-### account/verify_credentials (*; AUTH)
-#### Parameters
-
-* skip_status: Don't show the "status" field. (Default: false)
-* include_entities: "true" shows entities for pictures and links (Default: false)
-
----
-### conversation/show (*; AUTH)
-Unofficial Twitter command. It shows all direct answers (excluding the original post) to a given id.
-
-#### Parameter
-* id: id of the post
-* count: Items per page (default: 20)
-* page: page number
-* since_id: minimal id
-* max_id: maximum id
-* include_entities: "true" shows entities for pictures and links (Default: false)
-
-#### Unsupported parameters
-* include_rts
-* trim_user
-* contributor_details
-
----
-### direct_messages (*; AUTH)
-#### Parameters
-* count: Items per page (default: 20)
-* page: page number
-* since_id: minimal id
-* max_id: maximum id
-* getText: Defines the format of the status field. Can be "html" or "plain"
-* include_entities: "true" shows entities for pictures and links (Default: false)
-* friendica_verbose: "true" enables different error returns (default: "false")
-
-#### Unsupported parameters
-* skip_status
-
----
-### direct_messages/all (*; AUTH)
-#### Parameters
-* count: Items per page (default: 20)
-* page: page number
-* since_id: minimal id
-* max_id: maximum id
-* getText: Defines the format of the status field. Can be "html" or "plain"
-* friendica_verbose: "true" enables different error returns (default: "false")
-
----
-### direct_messages/conversation (*; AUTH)
-Shows all direct messages of a conversation
-#### Parameters
-* count: Items per page (default: 20)
-* page: page number
-* since_id: minimal id
-* max_id: maximum id
-* getText: Defines the format of the status field. Can be "html" or "plain"
-* uri: URI of the conversation
-* friendica_verbose: "true" enables different error returns (default: "false")
-
----
-### direct_messages/sent (*; AUTH)
-#### Parameters
-* count: Items per page (default: 20)
-* page: page number
-* since_id: minimal id
-* max_id: maximum id
-* getText: Defines the format of the status field. Can be "html" or "plain"
-* include_entities: "true" shows entities for pictures and links (Default: false)
-* friendica_verbose: "true" enables different error returns (default: "false")
-
----
-### direct_messages/new (POST,PUT; AUTH)
-#### Parameters
-* user_id: id of the user
-* screen_name: screen name (for technical reasons, this value is not unique!)
-* text: The message
-* replyto: ID of the replied direct message
-* title: Title of the direct message
-
----
-### direct_messages/destroy (POST,DELETE; AUTH)
-#### Parameters
-* id: id of the message to be deleted
-* include_entities: optional, currently not yet implemented
-* friendica_parenturi: optional, can be used for increased safety to delete only intended messages
-* friendica_verbose: "true" enables different error returns (default: "false")
-
-#### Return values
-
-On success:
-* JSON return as defined for Twitter API not yet implemented
-* on friendica_verbose=true: JSON return {"result":"ok","message":"message deleted"}
-
-On error:
-HTTP 400 BadRequest
-* on friendica_verbose=true: different JSON returns {"result":"error","message":"xyz"}
-
----
-### favorites (*; AUTH)
-#### Parameters
-* count: Items per page (default: 20)
-* page: page number
-* since_id: minimal id
-* max_id: maximum id
-* include_entities: "true" shows entities for pictures and links (Default: false)
-
-#### Unsupported parameters
-* user_id
-* screen_name
-
-Favorites aren't displayed to other users, so "user_id" and "screen_name" are unsupported.
-Set this values will result in an empty array.
-
----
-### favorites/create (POST,PUT; AUTH)
-#### Parameters
-* id
-* include_entities: "true" shows entities for pictures and links (Default: false)
-
----
-### favorites/destroy (POST,DELETE; AUTH)
-#### Parameters
-* id
-* include_entities: "true" shows entities for pictures and links (Default: false)
-
----
-### followers/ids (*; AUTH)
-#### Parameters
-* stringify_ids: Should the id numbers be sent as text (true) or number (false)? (default: false)
-
-#### Unsupported parameters
-* user_id
-* screen_name
-* cursor
-
-Friendica doesn't allow showing followers of other users.
-
----
-### friends/ids (*; AUTH)
-#### Parameters
-* stringify_ids: Should the id numbers be sent as text (true) or number (false)? (default: false)
-
-#### Unsupported parameters
-* user_id
-* screen_name
-* cursor
-
-Friendica doesn't allow showing friends of other users.
-
----
-### help/test (*)
-
----
-### media/upload (POST,PUT; AUTH)
-#### Parameters
-* media: image data
-
----
-### oauth/request_token (*)
-#### Parameters
-* oauth_callback
-
-#### Unsupported parameters
-* x_auth_access_type
-
----
-### oauth/access_token (*)
-#### Parameters
-* oauth_verifier
-
-#### Unsupported parameters
-* x_auth_password
-* x_auth_username
-* x_auth_mode
-
----
-### statuses/destroy (POST,DELETE; AUTH)
-#### Parameters
-* id: message number
-* include_entities: "true" shows entities for pictures and links (Default: false)
-
-#### Unsupported parameters
-* trim_user
-
----
-### statuses/followers (*; AUTH)
-
-#### Parameters
-
-* include_entities: "true" shows entities for pictures and links (Default: false)
-
----
-### statuses/friends (*; AUTH)
-
-#### Parameters
-
-* include_entities: "true" shows entities for pictures and links (Default: false)
-
----
-### statuses/friends_timeline (*; AUTH)
-#### Parameters
-* count: Items per page (default: 20)
-* page: page number
-* since_id: minimal id
-* max_id: maximum id
-* exclude_replies: don't show replies (default: false)
-* conversation_id: Shows all statuses of a given conversation.
-* include_entities: "true" shows entities for pictures and links (Default: false)
-
-#### Unsupported parameters
-* include_rts
-* trim_user
-* contributor_details
-
----
-### statuses/home_timeline (*; AUTH)
-#### Parameters
-* count: Items per page (default: 20)
-* page: page number
-* since_id: minimal id
-* max_id: maximum id
-* exclude_replies: don't show replies (default: false)
-* conversation_id: Shows all statuses of a given conversation.
-* include_entities: "true" shows entities for pictures and links (Default: false)
-
-#### Unsupported parameters
-* include_rts
-* trim_user
-* contributor_details
-
----
-### statuses/mentions (*; AUTH)
-#### Parameters
-* count: Items per page (default: 20)
-* page: page number
-* since_id: minimal id
-* max_id: maximum id
-* include_entities: "true" shows entities for pictures and links (Default: false)
-
-#### Unsupported parameters
-* include_rts
-* trim_user
-* contributor_details
-
----
-### statuses/public_timeline (*; AUTH)
-#### Parameters
-* count: Items per page (default: 20)
-* page: page number
-* since_id: minimal id
-* max_id: maximum id
-* exclude_replies: don't show replies (default: false)
-* conversation_id: Shows all statuses of a given conversation.
-* include_entities: "true" shows entities for pictures and links (Default: false)
-
-#### Unsupported parameters
-* trim_user
-
----
-### statuses/replies (*; AUTH)
-#### Parameters
-* count: Items per page (default: 20)
-* page: page number
-* since_id: minimal id
-* max_id: maximum id
-* include_entities: "true" shows entities for pictures and links (Default: false)
-
-#### Unsupported parameters
-* include_rts
-* trim_user
-* contributor_details
-
----
-### statuses/retweet (POST,PUT; AUTH)
-#### Parameters
-* id: message number
-* include_entities: "true" shows entities for pictures and links (Default: false)
-
-#### Unsupported parameters
-* trim_user
-
----
-### statuses/show (*; AUTH)
-#### Parameters
-* id: message number
-* conversation: if set to "1" show all messages of the conversation with the given id
-* include_entities: "true" shows entities for pictures and links (Default: false)
-
-#### Unsupported parameters
-* include_my_retweet
-* trim_user
-
----
-### statuses/update, statuses/update_with_media
-#### Parameters
-* title: Title of the status
-* status: Status in text format
-* htmlstatus: Status in HTML format
-* in_reply_to_status_id
-* lat: latitude
-* long: longitude
-* media: image data
-* source: Application name
-* group_allow
-* contact_allow
-* group_deny
-* contact_deny
-* network
-* include_entities: "true" shows entities for pictures and links (Default: false)
-* media_ids: (By now only a single value, no array)
-
-#### Unsupported parameters
-* trim_user
-* place_id
-* display_coordinates
-
----
-### statuses/user_timeline (*; AUTH)
-#### Parameters
-* user_id: id of the user
-* screen_name: screen name (for technical reasons, this value is not unique!)
-* count: Items per page (default: 20)
-* page: page number
-* since_id: minimal id
-* max_id: maximum id
-* exclude_replies: don't show replies (default: false)
-* conversation_id: Shows all statuses of a given conversation.
-* include_entities: "true" shows entities for pictures and links (Default: false)
-
-#### Unsupported parameters
-
-* include_rts
-* trim_user
-* contributor_details
-
----
-### statusnet/config (*)
-
----
-### statusnet/conversation (*; AUTH)
-It shows all direct answers (excluding the original post) to a given id.
-
-#### Parameter
-* id: id of the post
-* count: Items per page (default: 20)
-* page: page number
-* since_id: minimal id
-* max_id: maximum id
-* include_entities: "true" shows entities for pictures and links (Default: false)
-
----
-### statusnet/version (*)
-
-#### Unsupported parameters
-* user_id
-* screen_name
-* cursor
-
-Friendica doesn't allow showing followers of other users.
-
----
-### users/search (*)
-#### Parameters
-* q: name of the user
-
-#### Unsupported parameters
-* page
-* count
-* include_entities
-
----
-### users/show (*)
-#### Parameters
-* user_id: id of the user
-* screen_name: screen name (for technical reasons, this value is not unique!)
-* include_entities: "true" shows entities for pictures and links (Default: false)
-
-#### Unsupported parameters
-* user_id
-* screen_name
-* cursor
-
-Friendica doesn't allow showing friends of other users.
-
-
-## Implemented API calls (not compatible with other APIs)
-
-
----
-### friendica/activity/
-#### parameters
-* id: item id
-
-Add or remove an activity from an item.
-'verb' can be one of:
-
-- like
-- dislike
-- attendyes
-- attendno
-- attendmaybe
-
-To remove an activity, prepend the verb with "un", eg. "unlike" or "undislike"
-Attend verbs disable eachother: that means that if "attendyes" was added to an item, adding "attendno" remove previous "attendyes".
-Attend verbs should be used only with event-related items (there is no check at the moment)
-
-#### Return values
-
-On success:
-json
-```"ok"```
-
-xml
-```true```
-
-On error:
-HTTP 400 BadRequest
-
----
-### friendica/group_show (*; AUTH)
-Return all or a specified group of the user with the containing contacts as array.
-
-#### Parameters
-* gid: optional, if not given, API returns all groups of the user
-
-#### Return values
-Array of:
-
-* name: name of the group
-* gid: id of the group
-* user: array of group members (return from api_get_user() function for each member)
-
-
----
-### friendica/group_delete (POST,DELETE; AUTH)
-delete the specified group of contacts; API call need to include the correct gid AND name of the group to be deleted.
-
-#### Parameters
-* gid: id of the group to be deleted
-* name: name of the group to be deleted
-
-#### Return values
-Array of:
-
-* success: true if successfully deleted
-* gid: gid of the deleted group
-* name: name of the deleted group
-* status: „deleted“ if successfully deleted
-* wrong users: empty array
-
-
----
-### friendica/group_create (POST,PUT; AUTH)
-Create the group with the posted array of contacts as members.
-
-#### Parameters
-* name: name of the group to be created
-
-#### POST data
-JSON data as Array like the result of "users/group_show":
-
-* gid
-* name
-* array of users
-
-#### Return values
-Array of:
-
-* success: true if successfully created or reactivated
-* gid: gid of the created group
-* name: name of the created group
-* status: „missing user“ | „reactivated“ | „ok“
-* wrong users: array of users, which were not available in the contact table
-
-
----
-### friendica/group_update (POST)
-Update the group with the posted array of contacts as members (post all members of the group to the call; function will remove members not posted).
-
-#### Parameters
-* gid: id of the group to be changed
-* name: name of the group to be changed
-
-#### POST data
-JSON data as array like the result of „users/group_show“:
-
-* gid
-* name
-* array of users
-
-#### Return values
-Array of:
-
-* success: true if successfully updated
-* gid: gid of the changed group
-* name: name of the changed group
-* status: „missing user“ | „ok“
-* wrong users: array of users, which were not available in the contact table
-
-
-
----
-### friendica/notifications (GET)
-Return last 50 notification for current user, ordered by date with unseen item on top
-
-#### Parameters
-none
-
-#### Return values
-Array of:
-
-* id: id of the note
-* type: type of notification as int (see NOTIFY_* constants in boot.php)
-* name: full name of the contact subject of the note
-* url: contact's profile url
-* photo: contact's profile photo
-* date: datetime string of the note
-* timestamp: timestamp of the node
-* date_rel: relative date of the note (eg. "1 hour ago")
-* msg: note message in bbcode
-* msg_html: note message in html
-* msg_plain: note message in plain text
-* link: link to note
-* seen: seen state: 0 or 1
-
-
----
-### friendica/notifications/seen (POST)
-Set note as seen, returns item object if possible
-
-#### Parameters
-id: id of the note to set seen
-
-#### Return values
-If the note is linked to an item, the item is returned, just like one of the "statuses/*_timeline" api.
-
-If the note is not linked to an item, a success status is returned:
-
-* "success" (json) | "<status>success</status>" (xml)
-
-
----
-### friendica/photo (*; AUTH)
-#### Parameters
-* photo_id: Resource id of a photo.
-* scale: (optional) scale value of the photo
-
-Returns data of a picture with the given resource.
-If 'scale' isn't provided, returned data include full url to each scale of the photo.
-If 'scale' is set, returned data include image data base64 encoded.
-
-possibile scale value are:
-
-* 0: original or max size by server settings
-* 1: image with or height at <= 640
-* 2: image with or height at <= 320
-* 3: thumbnail 160x160
-* 4: Profile image at 175x175
-* 5: Profile image at 80x80
-* 6: Profile image at 48x48
-
-An image used as profile image has only scale 4-6, other images only 0-3
-
-#### Return values
-
-json
-```
- {
- "id": "photo id"
- "created": "date(YYYY-MM-GG HH:MM:SS)",
- "edited": "date(YYYY-MM-GG HH:MM:SS)",
- "title": "photo title",
- "desc": "photo description",
- "album": "album name",
- "filename": "original file name",
- "type": "mime type",
- "height": "number",
- "width": "number",
- "profile": "1 if is profile photo",
- "link": {
- "": "url to image"
- ...
- },
- // if 'scale' is set
- "datasize": "size in byte",
- "data": "base64 encoded image data"
- }
-```
-
-xml
-```
-
- photo id
- date(YYYY-MM-GG HH:MM:SS)
- date(YYYY-MM-GG HH:MM:SS)
- photo title
- photo description
- album name
- original file name
- mime type
- number
- number
- 1 if is profile photo
-
-
- ...
-
-
-```
-
----
-### friendica/photos/list (*; AUTH)
-
-Returns a list of all photo resources of the logged in user.
-
-#### Return values
-
-json
-```
- [
- {
- id: "resource_id",
- album: "album name",
- filename: "original file name",
- type: "image mime type",
- thumb: "url to thumb sized image"
- },
- ...
- ]
-```
-
-xml
-```
-
-
- "url to thumb sized image"
-
- ...
-
-```
-
----
-### friendica/direct_messages_setseen (GET; AUTH)
-#### Parameters
-* id: id of the message to be updated as seen
-
-#### Return values
-
-On success:
-* JSON return {"result":"ok","message":"message set to seen"}
-
-On error:
-* different JSON returns {"result":"error","message":"xyz"}
-
----
-### friendica/direct_messages_search (GET; AUTH)
-#### Parameters
-* searchstring: string for which the API call should search as '%searchstring%' in field 'body' of all messages of the authenticated user (caption ignored)
-
-#### Return values
-Returns only tested with JSON, XML might work as well.
-
-On success:
-* JSON return {"success":"true","search_results": array of found messages}
-* JSOn return {"success":"false","search_results":"nothing found"}
-
-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)
-
-* statuses/retweets_of_me
-* friendships/create
-* friendships/destroy
-* friendships/exists
-* friendships/show
-* account/update_profile_background_image
-* account/update_profile_image
-* blocks/create
-* blocks/destroy
-
-The following API calls from the Twitter API aren't implemented neither in Friendica nor in GNU Social:
-
-* statuses/mentions_timeline
-* statuses/retweets/:id
-* statuses/oembed
-* statuses/retweeters/ids
-* statuses/lookup
-* direct_messages/show
-* search/tweets
-* friendships/no_retweets/ids
-* friendships/incoming
-* friendships/outgoing
-* friendships/update
-* friends/list
-* friendships/lookup
-* account/settings
-* account/update_delivery_device
-* account/update_profile
-* account/update_profile_background_image
-* account/update_profile_image
-* blocks/list
-* blocks/ids
-* users/lookup
-* users/show
-* users/search
-* account/remove_profile_banner
-* account/update_profile_banner
-* users/profile_banner
-* mutes/users/create
-* mutes/users/destroy
-* mutes/users/ids
-* mutes/users/list
-* users/suggestions/:slug
-* users/suggestions
-* users/suggestions/:slug/members
-* favorites/list
-* lists/list
-* lists/statuses
-* lists/members/destroy
-* lists/memberships
-* lists/subscribers
-* lists/subscribers/create
-* lists/subscribers/show
-* lists/subscribers/destroy
-* lists/members/create_all
-* lists/members/show
-* lists/members
-* lists/members/create
-* lists/destroy
-* lists/update
-* lists/create
-* lists/show
-* lists/subscriptions
-* lists/members/destroy_all
-* lists/ownerships
-* saved_searches/list
-* saved_searches/show/:id
-* saved_searches/create
-* saved_searches/destroy/:id
-* geo/id/:place_id
-* geo/reverse_geocode
-* geo/search
-* geo/place
-* trends/place
-* trends/available
-* help/configuration
-* help/languages
-* help/privacy
-* help/tos
-* trends/closest
-* users/report_spam
-
----
-
----
-
-## Usage Examples
-### BASH / cURL
-Betamax has documentated some example API usage from a [bash script](https://en.wikipedia.org/wiki/Bash_(Unix_shell) employing [curl](https://en.wikipedia.org/wiki/CURL) (see [his posting](https://betamax65.de/display/betamax65/43539)).
-
-/usr/bin/curl -u USER:PASS https://YOUR.FRIENDICA.TLD/api/statuses/update.xml -d source="some source id" -d status="the status you want to post"
-
-### Python
-The [RSStoFriedika](https://github.com/pafcu/RSStoFriendika) code can be used as an example of how to use the API with python. The lines for posting are located at [line 21](https://github.com/pafcu/RSStoFriendika/blob/master/RSStoFriendika.py#L21) and following.
-
-def tweet(server, message, group_allow=None):
-url = server + '/api/statuses/update'
-urllib2.urlopen(url, urllib.urlencode({'status': message,'group_allow[]':group_allow}, doseq=True))
-
-There is also a [module for python 3](https://bitbucket.org/tobiasd/python-friendica) for using the API.
+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.
+
+## Implemented API calls
+
+### General
+#### HTTP Method
+
+API endpoints can restrict the method used to request them.
+Using an invalid method results in HTTP error 405 "Method Not Allowed".
+
+In this document, the required method is listed after the endpoint name. "*" means every method can be used.
+
+#### Auth
+
+Friendica supports basic http auth and OAuth 1 to authenticate the user to the api.
+
+OAuth settings can be added by the user in web UI under /settings/oauth/
+
+In this document, endpoints which requires auth are marked with "AUTH" after endpoint name
+
+#### Unsupported parameters
+* cursor: Not implemented in GNU Social
+* trim_user: Not implemented in GNU Social
+* contributor_details: Not implemented in GNU Social
+* place_id: Not implemented in GNU Social
+* display_coordinates: Not implemented in GNU Social
+* include_rts: To-Do
+* include_my_retweet: Retweets in Friendica are implemented in a different way
+
+#### Different behaviour
+* screen_name: The nick name in friendica is only unique in each network but not for all networks. The users are searched in the following priority: Friendica, StatusNet/GNU Social, Diaspora, pump.io, Twitter. If no contact was found by this way, then the first contact is taken.
+* include_entities: Default is "false". If set to "true" then the plain text is formatted so that links are having descriptions.
+
+#### Return values
+* cid: Contact id of the user (important for "contact_allow" and "contact_deny")
+* network: network of the user
+
+#### Errors
+When an error occour in API call, an HTTP error code is returned, with an error message
+Usually:
+- 400 Bad Request: if parameter are missing or items can't be found
+- 403 Forbidden: if authenticated user is missing
+- 405 Method Not Allowed: if API was called with invalid method, eg. GET when API require POST
+- 501 Not Implemented: if requested API doesn't exists
+- 500 Internal Server Error: on other error contitions
+
+Error body is
+
+json:
+```
+ {
+ "error": "Specific error message",
+ "request": "API path requested",
+ "code": "HTTP error code"
+ }
+```
+
+xml:
+```
+
+ Specific error message
+ API path requested
+ HTTP error code
+
+```
+
+---
+### account/rate_limit_status (*; AUTH)
+
+---
+### account/verify_credentials (*; AUTH)
+#### Parameters
+
+* skip_status: Don't show the "status" field. (Default: false)
+* include_entities: "true" shows entities for pictures and links (Default: false)
+
+---
+### conversation/show (*; AUTH)
+Unofficial Twitter command. It shows all direct answers (excluding the original post) to a given id.
+
+#### Parameter
+* id: id of the post
+* count: Items per page (default: 20)
+* page: page number
+* since_id: minimal id
+* max_id: maximum id
+* include_entities: "true" shows entities for pictures and links (Default: false)
+
+#### Unsupported parameters
+* include_rts
+* trim_user
+* contributor_details
+
+---
+### direct_messages (*; AUTH)
+#### Parameters
+* count: Items per page (default: 20)
+* page: page number
+* since_id: minimal id
+* max_id: maximum id
+* getText: Defines the format of the status field. Can be "html" or "plain"
+* include_entities: "true" shows entities for pictures and links (Default: false)
+* friendica_verbose: "true" enables different error returns (default: "false")
+
+#### Unsupported parameters
+* skip_status
+
+---
+### direct_messages/all (*; AUTH)
+#### Parameters
+* count: Items per page (default: 20)
+* page: page number
+* since_id: minimal id
+* max_id: maximum id
+* getText: Defines the format of the status field. Can be "html" or "plain"
+* friendica_verbose: "true" enables different error returns (default: "false")
+
+---
+### direct_messages/conversation (*; AUTH)
+Shows all direct messages of a conversation
+#### Parameters
+* count: Items per page (default: 20)
+* page: page number
+* since_id: minimal id
+* max_id: maximum id
+* getText: Defines the format of the status field. Can be "html" or "plain"
+* uri: URI of the conversation
+* friendica_verbose: "true" enables different error returns (default: "false")
+
+---
+### direct_messages/sent (*; AUTH)
+#### Parameters
+* count: Items per page (default: 20)
+* page: page number
+* since_id: minimal id
+* max_id: maximum id
+* getText: Defines the format of the status field. Can be "html" or "plain"
+* include_entities: "true" shows entities for pictures and links (Default: false)
+* friendica_verbose: "true" enables different error returns (default: "false")
+
+---
+### direct_messages/new (POST,PUT; AUTH)
+#### Parameters
+* user_id: id of the user
+* screen_name: screen name (for technical reasons, this value is not unique!)
+* text: The message
+* replyto: ID of the replied direct message
+* title: Title of the direct message
+
+---
+### direct_messages/destroy (POST,DELETE; AUTH)
+#### Parameters
+* id: id of the message to be deleted
+* include_entities: optional, currently not yet implemented
+* friendica_parenturi: optional, can be used for increased safety to delete only intended messages
+* friendica_verbose: "true" enables different error returns (default: "false")
+
+#### Return values
+
+On success:
+* JSON return as defined for Twitter API not yet implemented
+* on friendica_verbose=true: JSON return {"result":"ok","message":"message deleted"}
+
+On error:
+HTTP 400 BadRequest
+* on friendica_verbose=true: different JSON returns {"result":"error","message":"xyz"}
+
+---
+### externalprofile/show (*)
+#### Parameters
+* profileurl: profile url
+
+---
+### favorites (*; AUTH)
+#### Parameters
+* count: Items per page (default: 20)
+* page: page number
+* since_id: minimal id
+* max_id: maximum id
+* include_entities: "true" shows entities for pictures and links (Default: false)
+
+#### Unsupported parameters
+* user_id
+* screen_name
+
+Favorites aren't displayed to other users, so "user_id" and "screen_name" are unsupported.
+Set this values will result in an empty array.
+
+---
+### favorites/create (POST,PUT; AUTH)
+#### Parameters
+* id
+* include_entities: "true" shows entities for pictures and links (Default: false)
+
+---
+### favorites/destroy (POST,DELETE; AUTH)
+#### Parameters
+* id
+* include_entities: "true" shows entities for pictures and links (Default: false)
+
+---
+### followers/ids (*; AUTH)
+#### Parameters
+* stringify_ids: Should the id numbers be sent as text (true) or number (false)? (default: false)
+
+#### Unsupported parameters
+* user_id
+* screen_name
+* cursor
+
+Friendica doesn't allow showing followers of other users.
+
+---
+### friends/ids (*; AUTH)
+#### Parameters
+* stringify_ids: Should the id numbers be sent as text (true) or number (false)? (default: false)
+
+#### Unsupported parameters
+* user_id
+* screen_name
+* cursor
+
+Friendica doesn't allow showing friends of other users.
+
+---
+### help/test (*)
+
+---
+### media/upload (POST,PUT; AUTH)
+#### Parameters
+* media: image data
+
+---
+### oauth/request_token (*)
+#### Parameters
+* oauth_callback
+
+#### Unsupported parameters
+* x_auth_access_type
+
+---
+### oauth/access_token (*)
+#### Parameters
+* oauth_verifier
+
+#### Unsupported parameters
+* x_auth_password
+* x_auth_username
+* x_auth_mode
+
+---
+### statuses/destroy (POST,DELETE; AUTH)
+#### Parameters
+* id: message number
+* include_entities: "true" shows entities for pictures and links (Default: false)
+
+#### Unsupported parameters
+* trim_user
+
+---
+### statuses/followers (*; AUTH)
+
+#### Parameters
+
+* include_entities: "true" shows entities for pictures and links (Default: false)
+
+---
+### statuses/friends (*; AUTH)
+
+#### Parameters
+
+* include_entities: "true" shows entities for pictures and links (Default: false)
+
+---
+### statuses/friends_timeline (*; AUTH)
+#### Parameters
+* count: Items per page (default: 20)
+* page: page number
+* since_id: minimal id
+* max_id: maximum id
+* exclude_replies: don't show replies (default: false)
+* conversation_id: Shows all statuses of a given conversation.
+* include_entities: "true" shows entities for pictures and links (Default: false)
+
+#### Unsupported parameters
+* include_rts
+* trim_user
+* contributor_details
+
+---
+### statuses/home_timeline (*; AUTH)
+#### Parameters
+* count: Items per page (default: 20)
+* page: page number
+* since_id: minimal id
+* max_id: maximum id
+* exclude_replies: don't show replies (default: false)
+* conversation_id: Shows all statuses of a given conversation.
+* include_entities: "true" shows entities for pictures and links (Default: false)
+
+#### Unsupported parameters
+* include_rts
+* trim_user
+* contributor_details
+
+---
+### statuses/mentions (*; AUTH)
+#### Parameters
+* count: Items per page (default: 20)
+* page: page number
+* since_id: minimal id
+* max_id: maximum id
+* include_entities: "true" shows entities for pictures and links (Default: false)
+
+#### Unsupported parameters
+* include_rts
+* trim_user
+* contributor_details
+
+---
+### statuses/public_timeline (*; AUTH)
+#### Parameters
+* count: Items per page (default: 20)
+* page: page number
+* since_id: minimal id
+* max_id: maximum id
+* exclude_replies: don't show replies (default: false)
+* conversation_id: Shows all statuses of a given conversation.
+* include_entities: "true" shows entities for pictures and links (Default: false)
+
+#### Unsupported parameters
+* trim_user
+
+---
+### statuses/replies (*; AUTH)
+#### Parameters
+* count: Items per page (default: 20)
+* page: page number
+* since_id: minimal id
+* max_id: maximum id
+* include_entities: "true" shows entities for pictures and links (Default: false)
+
+#### Unsupported parameters
+* include_rts
+* trim_user
+* contributor_details
+
+---
+### statuses/retweet (POST,PUT; AUTH)
+#### Parameters
+* id: message number
+* include_entities: "true" shows entities for pictures and links (Default: false)
+
+#### Unsupported parameters
+* trim_user
+
+---
+### statuses/show (*; AUTH)
+#### Parameters
+* id: message number
+* conversation: if set to "1" show all messages of the conversation with the given id
+* include_entities: "true" shows entities for pictures and links (Default: false)
+
+#### Unsupported parameters
+* include_my_retweet
+* trim_user
+
+---
+### statuses/update, statuses/update_with_media
+#### Parameters
+* title: Title of the status
+* status: Status in text format
+* htmlstatus: Status in HTML format
+* in_reply_to_status_id
+* lat: latitude
+* long: longitude
+* media: image data
+* source: Application name
+* group_allow
+* contact_allow
+* group_deny
+* contact_deny
+* network
+* include_entities: "true" shows entities for pictures and links (Default: false)
+* media_ids: (By now only a single value, no array)
+
+#### Unsupported parameters
+* trim_user
+* place_id
+* display_coordinates
+
+---
+### statuses/user_timeline (*; AUTH)
+#### Parameters
+* user_id: id of the user
+* screen_name: screen name (for technical reasons, this value is not unique!)
+* count: Items per page (default: 20)
+* page: page number
+* since_id: minimal id
+* max_id: maximum id
+* exclude_replies: don't show replies (default: false)
+* conversation_id: Shows all statuses of a given conversation.
+* include_entities: "true" shows entities for pictures and links (Default: false)
+
+#### Unsupported parameters
+
+* include_rts
+* trim_user
+* contributor_details
+
+---
+### statusnet/config (*)
+
+---
+### statusnet/conversation (*; AUTH)
+It shows all direct answers (excluding the original post) to a given id.
+
+#### Parameter
+* id: id of the post
+* count: Items per page (default: 20)
+* page: page number
+* since_id: minimal id
+* max_id: maximum id
+* include_entities: "true" shows entities for pictures and links (Default: false)
+
+---
+### statusnet/version (*)
+
+#### Unsupported parameters
+* user_id
+* screen_name
+* cursor
+
+Friendica doesn't allow showing followers of other users.
+
+---
+### users/search (*)
+#### Parameters
+* q: name of the user
+
+#### Unsupported parameters
+* page
+* count
+* include_entities
+
+---
+### users/show (*)
+#### Parameters
+* user_id: id of the user
+* screen_name: screen name (for technical reasons, this value is not unique!)
+* include_entities: "true" shows entities for pictures and links (Default: false)
+
+#### Unsupported parameters
+* user_id
+* screen_name
+* cursor
+
+Friendica doesn't allow showing friends of other users.
+
+
+---
+### account/update_profile_image (POST; AUTH)
+#### Parameters
+* image: image data as base64 (Twitter has a limit of 700kb, Friendica allows more)
+* profile_id (optional): id of the profile for which the image should be used, default is changing the default profile
+
+uploads a new profile image (scales 4-6) to database, changes default or specified profile to the new photo
+
+#### Return values
+
+On success:
+* JSON return: returns the updated user details (see account/verify_credentials)
+
+On error:
+* 403 FORBIDDEN: if not authenticated
+* 400 BADREQUEST: "no media data submitted", "profile_id not available"
+* 500 INTERNALSERVERERROR: "image size exceeds PHP config settings, file was rejected by server",
+ "image size exceeds Friendica Config setting (uploaded size: x)",
+ "unable to process image data",
+ "image upload failed"
+
+
+## Implemented API calls (not compatible with other APIs)
+
+
+---
+### friendica/activity/
+#### parameters
+* id: item id
+
+Add or remove an activity from an item.
+'verb' can be one of:
+
+- like
+- dislike
+- attendyes
+- attendno
+- attendmaybe
+
+To remove an activity, prepend the verb with "un", eg. "unlike" or "undislike"
+Attend verbs disable eachother: that means that if "attendyes" was added to an item, adding "attendno" remove previous "attendyes".
+Attend verbs should be used only with event-related items (there is no check at the moment)
+
+#### Return values
+
+On success:
+json
+```"ok"```
+
+xml
+```true```
+
+On error:
+HTTP 400 BadRequest
+
+---
+### friendica/group_show (*; AUTH)
+Return all or a specified group of the user with the containing contacts as array.
+
+#### Parameters
+* gid: optional, if not given, API returns all groups of the user
+
+#### Return values
+Array of:
+
+* name: name of the group
+* gid: id of the group
+* user: array of group members (return from api_get_user() function for each member)
+
+
+---
+### friendica/group_delete (POST,DELETE; AUTH)
+delete the specified group of contacts; API call need to include the correct gid AND name of the group to be deleted.
+
+#### Parameters
+* gid: id of the group to be deleted
+* name: name of the group to be deleted
+
+#### Return values
+Array of:
+
+* success: true if successfully deleted
+* gid: gid of the deleted group
+* name: name of the deleted group
+* status: „deleted“ if successfully deleted
+* wrong users: empty array
+
+
+---
+### friendica/group_create (POST,PUT; AUTH)
+Create the group with the posted array of contacts as members.
+
+#### Parameters
+* name: name of the group to be created
+
+#### POST data
+JSON data as Array like the result of "users/group_show":
+
+* gid
+* name
+* array of users
+
+#### Return values
+Array of:
+
+* success: true if successfully created or reactivated
+* gid: gid of the created group
+* name: name of the created group
+* status: „missing user“ | „reactivated“ | „ok“
+* wrong users: array of users, which were not available in the contact table
+
+
+---
+### friendica/group_update (POST)
+Update the group with the posted array of contacts as members (post all members of the group to the call; function will remove members not posted).
+
+#### Parameters
+* gid: id of the group to be changed
+* name: name of the group to be changed
+
+#### POST data
+JSON data as array like the result of „users/group_show“:
+
+* gid
+* name
+* array of users
+
+#### Return values
+Array of:
+
+* success: true if successfully updated
+* gid: gid of the changed group
+* name: name of the changed group
+* status: „missing user“ | „ok“
+* wrong users: array of users, which were not available in the contact table
+
+
+
+---
+### friendica/notifications (GET)
+Return last 50 notification for current user, ordered by date with unseen item on top
+
+#### Parameters
+none
+
+#### Return values
+Array of:
+
+* id: id of the note
+* type: type of notification as int (see NOTIFY_* constants in boot.php)
+* name: full name of the contact subject of the note
+* url: contact's profile url
+* photo: contact's profile photo
+* date: datetime string of the note
+* timestamp: timestamp of the node
+* date_rel: relative date of the note (eg. "1 hour ago")
+* msg: note message in bbcode
+* msg_html: note message in html
+* msg_plain: note message in plain text
+* link: link to note
+* seen: seen state: 0 or 1
+
+
+---
+### friendica/notifications/seen (POST)
+Set note as seen, returns item object if possible
+
+#### Parameters
+id: id of the note to set seen
+
+#### Return values
+If the note is linked to an item, the item is returned, just like one of the "statuses/*_timeline" api.
+
+If the note is not linked to an item, a success status is returned:
+
+* "success" (json) | "<status>success</status>" (xml)
+
+
+---
+### friendica/photo (*; AUTH)
+#### Parameters
+* photo_id: Resource id of a photo.
+* scale: (optional) scale value of the photo
+
+Returns data of a picture with the given resource.
+If 'scale' isn't provided, returned data include full url to each scale of the photo.
+If 'scale' is set, returned data include image data base64 encoded.
+
+possibile scale value are:
+
+* 0: original or max size by server settings
+* 1: image with or height at <= 640
+* 2: image with or height at <= 320
+* 3: thumbnail 160x160
+* 4: Profile image at 175x175
+* 5: Profile image at 80x80
+* 6: Profile image at 48x48
+
+An image used as profile image has only scale 4-6, other images only 0-3
+
+#### Return values
+
+json
+```
+ {
+ "id": "photo id"
+ "created": "date(YYYY-MM-GG HH:MM:SS)",
+ "edited": "date(YYYY-MM-GG HH:MM:SS)",
+ "title": "photo title",
+ "desc": "photo description",
+ "album": "album name",
+ "filename": "original file name",
+ "type": "mime type",
+ "height": "number",
+ "width": "number",
+ "profile": "1 if is profile photo",
+ "link": {
+ "": "url to image"
+ ...
+ },
+ // if 'scale' is set
+ "datasize": "size in byte",
+ "data": "base64 encoded image data"
+ }
+```
+
+xml
+```
+
+ photo id
+ date(YYYY-MM-GG HH:MM:SS)
+ date(YYYY-MM-GG HH:MM:SS)
+ photo title
+ photo description
+ album name
+ original file name
+ mime type
+ number
+ number
+ 1 if is profile photo
+
+
+ ...
+
+
+```
+
+---
+### friendica/photos/list (*; AUTH)
+
+Returns a list of all photo resources of the logged in user.
+
+#### Return values
+
+json
+```
+ [
+ {
+ id: "resource_id",
+ album: "album name",
+ filename: "original file name",
+ type: "image mime type",
+ thumb: "url to thumb sized image"
+ },
+ ...
+ ]
+```
+
+xml
+```
+
+
+ "url to thumb sized image"
+
+ ...
+
+```
+
+---
+### friendica/photoalbum/delete (POST,DELETE; AUTH)
+#### Parameters
+* album: name of the album to be deleted
+
+deletes all images with the specified album name, is not reversible -> ensure that client is asking user for being sure to do this
+
+#### Return values
+
+On success:
+* JSON return {"result":"deleted","message":"album 'xyz' with all containing photos has been deleted."}
+
+On error:
+* 403 FORBIDDEN: if not authenticated
+* 400 BADREQUEST: "no albumname specified", "album not available"
+* 500 INTERNALSERVERERROR: "problem with deleting item occured", "unknown error - deleting from database failed"
+
+
+---
+### friendica/photoalbum/update (POST,PUT; AUTH)
+#### Parameters
+* album: name of the album to be updated
+* album_new: new name of the album
+
+changes the album name to album_new for all photos in album
+
+#### Return values
+
+On success:
+* JSON return {"result":"updated","message":"album 'abc' with all containing photos has been renamed to 'xyz'."}
+
+On error:
+* 403 FORBIDDEN: if not authenticated
+* 400 BADREQUEST: "no albumname specified", "no new albumname specified", "album not available"
+* 500 INTERNALSERVERERROR: "unknown error - updating in database failed"
+
+
+---
+### friendica/photo/create (POST; AUTH)
+### friendica/photo/update (POST; AUTH)
+#### Parameters
+* photo_id (optional): if specified the photo with this id will be updated
+* media (optional): image data as base64, only optional if photo_id is specified (new upload must have media)
+* desc (optional): description for the photo, updated when photo_id is specified
+* album: name of the album to be deleted (always necessary)
+* album_new (optional): can be used to change the album of a single photo if photo_id is specified
+* allow_cid/allow_gid/deny_cid/deny_gid (optional): on create: empty string or omitting = public photo, specify in format '``````' for private photo;
+ on update: keys need to be present with empty values for setting a private photo now to public
+
+both calls point to one function for creating AND updating photos.
+Saves data for the scales 0-2 to database (see above for scale description).
+Call adds non-visible entries to items table to enable authenticated contacts to comment/like the photo.
+Client should pay attention to the fact that updated access rights are not transferred to the contacts. i.e. public photos remain publicly visible if they have been commented/liked before setting visibility back to a limited group.
+Currently it is best way to inform user that updating rights is not the best way, offer a solution to add photo as a new photo with the new rights.
+
+#### Return values
+
+On success:
+* new photo uploaded: JSON return with photo data (see friendica/photo)
+* photo updated - changed photo data: JSON return with photo data (see friendica/photo)
+* photo updated - changed info: JSON return {"result":"updated","message":"Image id 'xyz' has been updated."}
+* photo updated - nothing changed: JSON return {"result":"cancelled","message":"Nothing to update for image id 'xyz'."}
+
+On error:
+* 403 FORBIDDEN: if not authenticated
+* 400 BADREQUEST: "no albumname specified", "no media data submitted", "photo not available", "acl data invalid"
+* 500 INTERNALSERVERERROR: "image size exceeds PHP config settings, file was rejected by server",
+ "image size exceeds Friendica Config setting (uploaded size: x)",
+ "unable to process image data",
+ "image upload failed",
+ "unknown error - uploading photo failed, see Friendica log for more information",
+ "unknown error - update photo entry in database failed",
+ "unknown error - this error on uploading or updating a photo should never happen"
+
+
+---
+### friendica/photo/delete (DELETE; AUTH)
+#### Parameters
+* photo_id: id of the photo to be deleted
+
+deletes a single image with the specified id, is not reversible -> ensure that client is asking user for being sure to do this
+Sets item table entries for this photo to deleted = 1
+
+#### Return values
+
+On success:
+* JSON return {"result":"deleted","message":"photo with id 'xyz' has been deleted from server."}
+
+On error:
+* 403 FORBIDDEN: if not authenticated
+* 400 BADREQUEST: "no photo_id specified", "photo not available"
+* 500 INTERNALSERVERERROR: "unknown error on deleting photo", "problem with deleting items occurred"
+
+
+---
+### friendica/direct_messages_setseen (GET; AUTH)
+#### Parameters
+* id: id of the message to be updated as seen
+
+#### Return values
+
+On success:
+* JSON return {"result":"ok","message":"message set to seen"}
+
+On error:
+* different JSON returns {"result":"error","message":"xyz"}
+
+---
+### friendica/direct_messages_search (GET; AUTH)
+#### Parameters
+* searchstring: string for which the API call should search as '%searchstring%' in field 'body' of all messages of the authenticated user (caption ignored)
+
+#### Return values
+Returns only tested with JSON, XML might work as well.
+
+On success:
+* JSON return {"success":"true","search_results": array of found messages}
+* JSOn return {"success":"false","search_results":"nothing found"}
+
+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)
+
+* statuses/retweets_of_me
+* friendships/create
+* friendships/destroy
+* friendships/exists
+* friendships/show
+* account/update_profile_background_image
+* blocks/create
+* blocks/destroy
+
+The following API calls from the Twitter API aren't implemented neither in Friendica nor in GNU Social:
+
+* statuses/mentions_timeline
+* statuses/retweets/:id
+* statuses/oembed
+* statuses/retweeters/ids
+* statuses/lookup
+* direct_messages/show
+* search/tweets
+* friendships/no_retweets/ids
+* friendships/incoming
+* friendships/outgoing
+* friendships/update
+* friends/list
+* friendships/lookup
+* account/settings
+* account/update_delivery_device
+* account/update_profile
+* account/update_profile_background_image
+* blocks/list
+* blocks/ids
+* users/lookup
+* users/show
+* users/search
+* account/remove_profile_banner
+* account/update_profile_banner
+* users/profile_banner
+* mutes/users/create
+* mutes/users/destroy
+* mutes/users/ids
+* mutes/users/list
+* users/suggestions/:slug
+* users/suggestions
+* users/suggestions/:slug/members
+* favorites/list
+* lists/list
+* lists/statuses
+* lists/members/destroy
+* lists/memberships
+* lists/subscribers
+* lists/subscribers/create
+* lists/subscribers/show
+* lists/subscribers/destroy
+* lists/members/create_all
+* lists/members/show
+* lists/members
+* lists/members/create
+* lists/destroy
+* lists/update
+* lists/create
+* lists/show
+* lists/subscriptions
+* lists/members/destroy_all
+* lists/ownerships
+* saved_searches/list
+* saved_searches/show/:id
+* saved_searches/create
+* saved_searches/destroy/:id
+* geo/id/:place_id
+* geo/reverse_geocode
+* geo/search
+* geo/place
+* trends/place
+* trends/available
+* help/configuration
+* help/languages
+* help/privacy
+* help/tos
+* trends/closest
+* users/report_spam
+
+---
+
+---
+
+## Usage Examples
+### BASH / cURL
+Betamax has documentated some example API usage from a [bash script](https://en.wikipedia.org/wiki/Bash_(Unix_shell) employing [curl](https://en.wikipedia.org/wiki/CURL) (see [his posting](https://betamax65.de/display/betamax65/43539)).
+
+/usr/bin/curl -u USER:PASS https://YOUR.FRIENDICA.TLD/api/statuses/update.xml -d source="some source id" -d status="the status you want to post"
+
+### Python
+The [RSStoFriedika](https://github.com/pafcu/RSStoFriendika) code can be used as an example of how to use the API with python. The lines for posting are located at [line 21](https://github.com/pafcu/RSStoFriendika/blob/master/RSStoFriendika.py#L21) and following.
+
+def tweet(server, message, group_allow=None):
+url = server + '/api/statuses/update'
+urllib2.urlopen(url, urllib.urlencode({'status': message,'group_allow[]':group_allow}, doseq=True))
+
+There is also a [module for python 3](https://bitbucket.org/tobiasd/python-friendica) for using the API.
diff --git a/doc/autoloader.md b/doc/autoloader.md
index 83f101044..5b894cb1a 100644
--- a/doc/autoloader.md
+++ b/doc/autoloader.md
@@ -1,192 +1,197 @@
-Autoloader with Composer
-==========
-
-* [Home](help)
- * [Developer Intro](help/Developers-Intro)
-
-Friendica uses [Composer](https://getcomposer.org) to manage dependencies libraries and the class autoloader both for libraries and namespaced Friendica classes.
-
-It's a command-line tool that downloads required libraries into the `vendor` folder and makes any namespaced class in `src` available through the whole application through `boot.php`.
-
-* [Using Composer](help/Composer)
-
-## A quick introduction to class autoloading
-
-The autoloader dynamically includes the file defining a class when it is first referenced, either by instantiating an object or simply making sure that it is available, without the need to explicitly use "require_once".
-
-Once it is set up you don't have to directly use it, you can directly use any class that is covered by the autoloader (currently `vendor` and `src`)
-
-Under the hood, Composer registers a callback with [`spl_autoload_register()`](http://php.net/manual/en/function.spl-autoload-register.php) that receives a class name as an argument and includes the corresponding class definition file.
-For more info about PHP autoloading, please refer to the [official PHP documentation](http://php.net/manual/en/language.oop5.autoload.php).
-
-### Example
-
-Let's say you have a PHP file in `src/` that define a very useful class:
-
-```php
- // src/ItemsManager.php
- getAll();
-
- // pass $items to template
- // return result
- }
-```
-
-That's a quite simple example, but look: no `require()`!
-If you need to use a class, you can simply use it and you don't need to do anything else.
-
-Going further: now we have a bunch of `*Manager` classes that cause some code duplication, let's define a `BaseManager` class, where we move all common code between all managers:
-
-```php
- // src/BaseManager.php
- getAll();
+
+ // pass $items to template
+ // return result
+}
+```
+
+That's a quite simple example, but look: no `require()`!
+If you need to use a class, you can simply use it and you don't need to do anything else.
+
+Going further: now we have a bunch of `*Manager` classes that cause some code duplication, let's define a `BaseManager` class, where we move all common code between all managers:
+
+```php
+// src/BaseManager.php
+
diff --git a/include/Emailer.php b/include/Emailer.php
index b0cdc3fe6..9df6ac36c 100644
--- a/include/Emailer.php
+++ b/include/Emailer.php
@@ -81,4 +81,3 @@ class Emailer {
return $res;
}
}
-?>
diff --git a/include/ForumManager.php b/include/ForumManager.php
index c2a20df29..8f38ed3a3 100644
--- a/include/ForumManager.php
+++ b/include/ForumManager.php
@@ -1,5 +1,7 @@
Notification identifier
* int 'total' => Total number of available network notifications
@@ -436,7 +436,7 @@ class NotificationsManager {
}
/**
- * @brief Total number of system notifications
+ * @brief Total number of system notifications
* @param int|string $seen
* If 0 only include notifications into the query
* which aren't marked as "seen"
@@ -460,13 +460,13 @@ class NotificationsManager {
/**
* @brief Get system notifications
- *
+ *
* @param int|string $seen
* If 0 only include notifications into the query
* which aren't marked as "seen"
* @param int $start Start the query at this point
* @param int $limit Maximum number of query results
- *
+ *
* @return array with
* string 'ident' => Notification identifier
* int 'total' => Total number of available system notifications
@@ -502,7 +502,7 @@ class NotificationsManager {
/**
* @brief Addional SQL query string for the personal notifications
- *
+ *
* @return string The additional sql query
*/
private function _personal_sql_extra() {
@@ -520,7 +520,7 @@ class NotificationsManager {
}
/**
- * @brief Total number of personal notifications
+ * @brief Total number of personal notifications
* @param int|string $seen
* If 0 only include notifications into the query
* which aren't marked as "seen"
@@ -550,13 +550,13 @@ class NotificationsManager {
/**
* @brief Get personal notifications
- *
+ *
* @param int|string $seen
* If 0 only include notifications into the query
* which aren't marked as "seen"
* @param int $start Start the query at this point
* @param int $limit Maximum number of query results
- *
+ *
* @return array with
* string 'ident' => Notification identifier
* int 'total' => Total number of available personal notifications
@@ -573,13 +573,13 @@ class NotificationsManager {
$sql_seen = " AND `item`.`unseen` = 1 ";
$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`
+ `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`
FROM `item` INNER JOIN `item` AS `pitem` ON `pitem`.`id`=`item`.`parent`
WHERE `item`.`visible` = 1
$sql_extra
$sql_seen
- AND `item`.`deleted` = 0 AND `item`.`uid` = %d AND `item`.`wall` = 0
+ AND `item`.`deleted` = 0 AND `item`.`uid` = %d AND `item`.`wall` = 0
ORDER BY `item`.`created` DESC LIMIT %d, %d " ,
intval(local_user()),
intval($start),
@@ -588,7 +588,7 @@ class NotificationsManager {
if (dbm::is_result($r))
$notifs = $this->formatNotifs($r, $ident);
-
+
$arr = array (
'notifications' => $notifs,
'ident' => $ident,
@@ -599,7 +599,7 @@ class NotificationsManager {
}
/**
- * @brief Total number of home notifications
+ * @brief Total number of home notifications
* @param int|string $seen
* If 0 only include notifications into the query
* which aren't marked as "seen"
@@ -626,13 +626,13 @@ class NotificationsManager {
/**
* @brief Get home notifications
- *
+ *
* @param int|string $seen
* If 0 only include notifications into the query
* which aren't marked as "seen"
* @param int $start Start the query at this point
* @param int $limit Maximum number of query results
- *
+ *
* @return array with
* string 'ident' => Notification identifier
* int 'total' => Total number of available home notifications
@@ -673,7 +673,7 @@ class NotificationsManager {
}
/**
- * @brief Total number of introductions
+ * @brief Total number of introductions
* @param bool $all
* If false only include introductions into the query
* which aren't marked as ignored
@@ -698,13 +698,13 @@ class NotificationsManager {
/**
* @brief Get introductions
- *
+ *
* @param bool $all
* If false only include introductions into the query
* which aren't marked as ignored
* @param int $start Start the query at this point
* @param int $limit Maximum number of query results
- *
+ *
* @return array with
* string 'ident' => Notification identifier
* int 'total' => Total number of available introductions
@@ -749,7 +749,7 @@ class NotificationsManager {
/**
* @brief Format the notification query in an usable array
- *
+ *
* @param array $intros The array from the db query
* @return array with the introductions
*/
diff --git a/include/Photo.php b/include/Photo.php
index 828dce82d..734193576 100644
--- a/include/Photo.php
+++ b/include/Photo.php
@@ -4,6 +4,8 @@
* @brief This file contains the Photo class for image processing
*/
+use Friendica\App;
+
require_once("include/photos.php");
class Photo {
@@ -68,7 +70,9 @@ class Photo {
$this->image->destroy();
return;
}
- imagedestroy($this->image);
+ if (is_resource($this->image)) {
+ imagedestroy($this->image);
+ }
}
}
@@ -324,6 +328,7 @@ class Photo {
return;
}
+ // if script dies at this point check memory_limit setting in php.ini
$this->image = imagerotate($this->image,$degrees,0);
$this->width = imagesx($this->image);
$this->height = imagesy($this->image);
@@ -620,7 +625,7 @@ class Photo {
- public function store($uid, $cid, $rid, $filename, $album, $scale, $profile = 0, $allow_cid = '', $allow_gid = '', $deny_cid = '', $deny_gid = '') {
+ public function store($uid, $cid, $rid, $filename, $album, $scale, $profile = 0, $allow_cid = '', $allow_gid = '', $deny_cid = '', $deny_gid = '', $desc = '') {
$r = q("SELECT `guid` FROM `photo` WHERE `resource-id` = '%s' AND `guid` != '' LIMIT 1",
dbesc($rid)
@@ -657,7 +662,8 @@ class Photo {
`allow_cid` = '%s',
`allow_gid` = '%s',
`deny_cid` = '%s',
- `deny_gid` = '%s'
+ `deny_gid` = '%s',
+ `desc` = '%s'
WHERE `id` = %d",
intval($uid),
@@ -679,12 +685,13 @@ class Photo {
dbesc($allow_gid),
dbesc($deny_cid),
dbesc($deny_gid),
+ dbesc($desc),
intval($x[0]['id'])
);
} else {
$r = q("INSERT INTO `photo`
- (`uid`, `contact-id`, `guid`, `resource-id`, `created`, `edited`, `filename`, type, `album`, `height`, `width`, `datasize`, `data`, `scale`, `profile`, `allow_cid`, `allow_gid`, `deny_cid`, `deny_gid`)
- VALUES (%d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s', %d, %d, '%s', '%s', '%s', '%s')",
+ (`uid`, `contact-id`, `guid`, `resource-id`, `created`, `edited`, `filename`, type, `album`, `height`, `width`, `datasize`, `data`, `scale`, `profile`, `allow_cid`, `allow_gid`, `deny_cid`, `deny_gid`, `desc`)
+ VALUES (%d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s', %d, %d, '%s', '%s', '%s', '%s', '%s')",
intval($uid),
intval($cid),
dbesc($guid),
@@ -703,7 +710,8 @@ class Photo {
dbesc($allow_cid),
dbesc($allow_gid),
dbesc($deny_cid),
- dbesc($deny_gid)
+ dbesc($deny_gid),
+ dbesc($desc)
);
}
diff --git a/include/Smilies.php b/include/Smilies.php
index d67b92d8b..f4de32f8f 100644
--- a/include/Smilies.php
+++ b/include/Smilies.php
@@ -5,6 +5,8 @@
* @brief This file contains the Smilies class which contains functions to handle smiles
*/
+use Friendica\App;
+
/**
* This class contains functions to handle smiles
*/
@@ -13,13 +15,13 @@ class Smilies {
/**
* @brief Function to list all smilies
- *
+ *
* Get an array of all smilies, both internal and from addons.
- *
+ *
* @return array
* 'texts' => smilie shortcut
* 'icons' => icon in html
- *
+ *
* @hook smilie ('texts' => smilies texts array, 'icons' => smilies html array)
*/
public static function get_list() {
@@ -64,41 +66,41 @@ class Smilies {
);
$icons = array(
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '~friendica ',
- 'redmatrix',
- 'redmatrix'
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '~friendica ',
+ 'redmatrix',
+ 'redmatrix'
);
$params = array('texts' => $texts, 'icons' => $icons);
@@ -121,7 +123,7 @@ class Smilies {
*
* @param string $s
* @param boolean $sample
- *
+ *
* @return string HML Output of the Smilie
*/
public static function replace($s, $sample = false) {
@@ -166,7 +168,7 @@ class Smilies {
*
* @param string $x
* @return string HTML Output
- *
+ *
* @todo: Rework because it doesn't work correctly
*/
private function preg_heart($x) {
@@ -174,7 +176,7 @@ class Smilies {
return $x[0];
$t = '';
for($cnt = 0; $cnt < strlen($x[1]); $cnt ++)
- $t .= '';
+ $t .= '';
$r = str_replace($x[0],$t,$x[0]);
return $r;
}
diff --git a/include/acl_selectors.php b/include/acl_selectors.php
index f57af575a..c7c6bb206 100644
--- a/include/acl_selectors.php
+++ b/include/acl_selectors.php
@@ -4,6 +4,8 @@
* @file include/acl_selectors.php
*/
+use Friendica\App;
+
require_once "include/contact_selectors.php";
require_once "include/contact_widgets.php";
require_once "include/DirSearch.php";
@@ -543,7 +545,7 @@ function acl_lookup(App $a, $out_type = 'json') {
if ($type == '') {
- $r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, `forum`, `prv` FROM `contact`
+ $r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, `addr`, `forum`, `prv` FROM `contact`
WHERE `uid` = %d AND NOT `self` AND NOT `blocked` AND NOT `pending` AND NOT `archive` AND `notify` != ''
AND NOT (`network` IN ('%s', '%s'))
$sql_extra2
@@ -552,7 +554,7 @@ function acl_lookup(App $a, $out_type = 'json') {
dbesc(NETWORK_OSTATUS), dbesc(NETWORK_STATUSNET)
);
} elseif ($type == 'c') {
- $r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, `forum`, `prv` FROM `contact`
+ $r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, `addr`, `forum`, `prv` FROM `contact`
WHERE `uid` = %d AND NOT `self` AND NOT `blocked` AND NOT `pending` AND NOT `archive` AND `notify` != ''
AND NOT (`network` IN ('%s'))
$sql_extra2
@@ -562,7 +564,7 @@ function acl_lookup(App $a, $out_type = 'json') {
);
}
elseif ($type == 'm') {
- $r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag` FROM `contact`
+ $r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, `addr` FROM `contact`
WHERE `uid` = %d AND NOT `self` AND NOT `blocked` AND NOT `pending` AND NOT `archive`
AND `network` IN ('%s','%s','%s')
$sql_extra2
@@ -573,7 +575,7 @@ function acl_lookup(App $a, $out_type = 'json') {
dbesc(NETWORK_DIASPORA)
);
} elseif ($type == 'a') {
- $r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, `forum`, `prv` FROM `contact`
+ $r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, `addr`, `forum`, `prv` FROM `contact`
WHERE `uid` = %d AND `pending` = 0
$sql_extra2
ORDER BY `name` ASC ",
@@ -617,6 +619,7 @@ function acl_lookup(App $a, $out_type = 'json') {
'network' => $g['network'],
'link' => $g['url'],
'nick' => htmlentities(($g['attag']) ? $g['attag'] : $g['nick']),
+ 'addr' => htmlentities(($g['addr']) ? $g['addr'] : $g['url']),
'forum' => ((x($g, 'forum') || x($g, 'prv')) ? 1 : 0),
);
}
@@ -661,6 +664,7 @@ function acl_lookup(App $a, $out_type = 'json') {
'network' => $contact['network'],
'link' => $contact['url'],
'nick' => htmlentities($contact['nick'] ? : $contact['addr']),
+ 'addr' => htmlentities(($contact['addr']) ? $contact['addr'] : $contact['url']),
'forum' => $contact['forum']
);
}
diff --git a/include/api.php b/include/api.php
index 97c09c20a..67e191725 100644
--- a/include/api.php
+++ b/include/api.php
@@ -6,7 +6,8 @@
* @todo Automatically detect if incoming data is HTML or BBCode
*/
-use \Friendica\Core\Config;
+use Friendica\App;
+use Friendica\Core\Config;
require_once 'include/HTTPExceptions.php';
require_once 'include/bbcode.php';
@@ -525,6 +526,15 @@ $called_api = null;
}
}
+ if (is_null($user) && x($_GET, 'profileurl')) {
+ $user = dbesc(normalise_link($_GET['profileurl']));
+ $nick = $user;
+ $extra_query = "AND `contact`.`nurl` = '%s' ";
+ if (api_user() !== false) {
+ $extra_query .= "AND `contact`.`uid`=".intval(api_user());
+ }
+ }
+
if (is_null($user) AND ($a->argc > (count($called_api) - 1)) AND (count($called_api) > 0)) {
$argid = count($called_api);
list($user, $null) = explode(".", $a->argv[$argid]);
@@ -1400,6 +1410,7 @@ $called_api = null;
/// @TODO move to top of file or somewhere better
api_register_func('api/users/show','api_users_show');
+ api_register_func('api/externalprofile/show','api_users_show');
function api_users_search($type) {
@@ -3276,14 +3287,117 @@ $called_api = null;
api_register_func('api/oauth/request_token', 'api_oauth_request_token', false);
api_register_func('api/oauth/access_token', 'api_oauth_access_token', false);
+
+ /**
+ * @brief delete a complete photoalbum with all containing photos from database through api
+ *
+ * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
+ * @return string
+ */
+ function api_fr_photoalbum_delete($type) {
+ if (api_user() === false) {
+ throw new ForbiddenException();
+ }
+ // input params
+ $album = (x($_REQUEST,'album') ? $_REQUEST['album'] : "");
+
+ // we do not allow calls without album string
+ if ($album == "") {
+ throw new BadRequestException("no albumname specified");
+ }
+ // check if album is existing
+ $r = q("SELECT DISTINCT `resource-id` FROM `photo` WHERE `uid` = %d AND `album` = '%s'",
+ intval(api_user()),
+ dbesc($album));
+ if (!dbm::is_result($r))
+ throw new BadRequestException("album not available");
+
+ // function for setting the items to "deleted = 1" which ensures that comments, likes etc. are not shown anymore
+ // to the user and the contacts of the users (drop_items() performs the federation of the deletion to other networks
+ foreach ($r as $rr) {
+ $photo_item = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `resource-id` = '%s' AND `type` = 'photo'",
+ intval(local_user()),
+ dbesc($rr['resource-id'])
+ );
+
+ if (!dbm::is_result($photo_item)) {
+ throw new InternalServerErrorException("problem with deleting items occured");
+ }
+ drop_item($photo_item[0]['id'],false);
+ }
+
+ // now let's delete all photos from the album
+ $result = q("DELETE FROM `photo` WHERE `uid` = %d AND `album` = '%s'",
+ intval(api_user()),
+ dbesc($album));
+
+ // return success of deletion or error message
+ if ($result) {
+ $answer = array('result' => 'deleted', 'message' => 'album `' . $album . '` with all containing photos has been deleted.');
+ return api_format_data("photoalbum_delete", $type, array('$result' => $answer));
+ } else {
+ throw new InternalServerErrorException("unknown error - deleting from database failed");
+ }
+
+ }
+
+ /**
+ * @brief update the name of the album for all photos of an album
+ *
+ * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
+ * @return string
+ */
+ function api_fr_photoalbum_update($type) {
+ if (api_user() === false) {
+ throw new ForbiddenException();
+ }
+ // input params
+ $album = (x($_REQUEST,'album') ? $_REQUEST['album'] : "");
+ $album_new = (x($_REQUEST,'album_new') ? $_REQUEST['album_new'] : "");
+
+ // we do not allow calls without album string
+ if ($album == "") {
+ throw new BadRequestException("no albumname specified");
+ }
+ if ($album_new == "") {
+ throw new BadRequestException("no new albumname specified");
+ }
+ // check if album is existing
+ $r = q("SELECT `id` FROM `photo` WHERE `uid` = %d AND `album` = '%s'",
+ intval(api_user()),
+ dbesc($album));
+ if (!dbm::is_result($r)) {
+ throw new BadRequestException("album not available");
+ }
+ // now let's update all photos to the albumname
+ $result = q("UPDATE `photo` SET `album` = '%s' WHERE `uid` = %d AND `album` = '%s'",
+ dbesc($album_new),
+ intval(api_user()),
+ dbesc($album));
+
+ // return success of updating or error message
+ if ($result) {
+ $answer = array('result' => 'updated', 'message' => 'album `' . $album . '` with all containing photos has been renamed to `' . $album_new . '`.');
+ return api_format_data("photoalbum_update", $type, array('$result' => $answer));
+ } else {
+ throw new InternalServerErrorException("unknown error - updating in database failed");
+ }
+ }
+
+
+ /**
+ * @brief list all photos of the authenticated user
+ *
+ * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
+ * @return string
+ */
function api_fr_photos_list($type) {
if (api_user() === false) {
throw new ForbiddenException();
}
-
- $r = q("SELECT `resource-id`, MAX(`scale`) AS `scale`, `album`, `filename`, `type`
- FROM `photo`
- WHERE `uid` = %d AND `album` != 'Contact Photos' GROUP BY `resource-id`, `album`, `filename`, `type`",
+ $r = q("SELECT `resource-id`, MAX(scale) AS `scale`, `album`, `filename`, `type`, MAX(`created`) AS `created`,
+ MAX(`edited`) AS `edited`, MAX(`desc`) AS `desc` FROM `photo`
+ WHERE `uid` = %d AND `album` != 'Contact Photos' GROUP BY `resource-id`",
intval(local_user())
);
$typetoext = array(
@@ -3291,7 +3405,7 @@ $called_api = null;
'image/png' => 'png',
'image/gif' => 'gif'
);
- $data = array('photo' => array());
+ $data = array('photo'=>array());
if (dbm::is_result($r)) {
foreach ($r as $rr) {
$photo = array();
@@ -3300,6 +3414,9 @@ $called_api = null;
$photo['filename'] = $rr['filename'];
$photo['type'] = $rr['type'];
$thumb = App::get_baseurl() . "/photo/" . $rr['resource-id'] . "-" . $rr['scale'] . "." . $typetoext[$rr['type']];
+ $photo['created'] = $rr['created'];
+ $photo['edited'] = $rr['edited'];
+ $photo['desc'] = $rr['desc'];
if ($type == "xml") {
$data['photo'][] = array("@attributes" => $photo, "1" => $thumb);
@@ -3312,26 +3429,563 @@ $called_api = null;
return api_format_data("photos", $type, $data);
}
+ /**
+ * @brief upload a new photo or change an existing photo
+ *
+ * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
+ * @return string
+ */
+ function api_fr_photo_create_update($type) {
+ if (api_user() === false) {
+ throw new ForbiddenException();
+ }
+ // input params
+ $photo_id = (x($_REQUEST, 'photo_id') ? $_REQUEST['photo_id'] : null);
+ $desc = (x($_REQUEST, 'desc') ? $_REQUEST['desc'] : (array_key_exists('desc', $_REQUEST) ? "" : null)); // extra check necessary to distinguish between 'not provided' and 'empty string'
+ $album = (x($_REQUEST,'album') ? $_REQUEST['album'] : null);
+ $album_new = (x($_REQUEST,'album_new') ? $_REQUEST['album_new'] : null);
+ $allow_cid = (x($_REQUEST, 'allow_cid') ? $_REQUEST['allow_cid'] : (array_key_exists('allow_cid', $_REQUEST) ? " " : null));
+ $deny_cid = (x($_REQUEST, 'deny_cid') ? $_REQUEST['deny_cid'] : (array_key_exists('deny_cid', $_REQUEST) ? " " : null));
+ $allow_gid = (x($_REQUEST, 'allow_gid') ? $_REQUEST['allow_gid'] : (array_key_exists('allow_gid', $_REQUEST) ? " " : null));
+ $deny_gid = (x($_REQUEST, 'deny_gid') ? $_REQUEST['deny_gid'] : (array_key_exists('deny_gid', $_REQUEST) ? " " : null));
+ $visibility = (x($_REQUEST, 'visibility') ? (($_REQUEST['visibility'] == "true" || $_REQUEST['visibility'] == 1) ? true : false) : false);
+
+ // do several checks on input parameters
+ // we do not allow calls without album string
+ if ($album == null) {
+ throw new BadRequestException("no albumname specified");
+ }
+ // if photo_id == null --> we are uploading a new photo
+ if ($photo_id == null) {
+ $mode = "create";
+
+ // error if no media posted in create-mode
+ if (!x($_FILES,'media')) {
+ // Output error
+ throw new BadRequestException("no media data submitted");
+ }
+
+ // album_new will be ignored in create-mode
+ $album_new = "";
+ } else {
+ $mode = "update";
+
+ // check if photo is existing in database
+ $r = q("SELECT `id` FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s' AND `album` = '%s'",
+ intval(api_user()),
+ dbesc($photo_id),
+ dbesc($album));
+ if (!dbm::is_result($r)) {
+ throw new BadRequestException("photo not available");
+ }
+ }
+
+ // checks on acl strings provided by clients
+ $acl_input_error = false;
+ $acl_input_error |= check_acl_input($allow_cid);
+ $acl_input_error |= check_acl_input($deny_cid);
+ $acl_input_error |= check_acl_input($allow_gid);
+ $acl_input_error |= check_acl_input($deny_gid);
+ if ($acl_input_error) {
+ throw new BadRequestException("acl data invalid");
+ }
+ // now let's upload the new media in create-mode
+ if ($mode == "create") {
+ $media = $_FILES['media'];
+ $data = save_media_to_database("photo", $media, $type, $album, trim($allow_cid), trim($deny_cid), trim($allow_gid), trim($deny_gid), $desc, $visibility);
+
+ // return success of updating or error message
+ if (!is_null($data)) {
+ return api_format_data("photo_create", $type, $data);
+ } else {
+ throw new InternalServerErrorException("unknown error - uploading photo failed, see Friendica log for more information");
+ }
+ }
+
+ // now let's do the changes in update-mode
+ if ($mode == "update") {
+ $sql_extra = "";
+
+ if (!is_null($desc)) {
+ $sql_extra .= (($sql_extra != "") ? " ," : "") . "`desc` = '$desc'";
+ }
+
+ if (!is_null($album_new)) {
+ $sql_extra .= (($sql_extra != "") ? " ," : "") . "`album` = '$album_new'";
+ }
+
+ if (!is_null($allow_cid)) {
+ $allow_cid = trim($allow_cid);
+ $sql_extra .= (($sql_extra != "") ? " ," : "") . "`allow_cid` = '$allow_cid'";
+ }
+
+ if (!is_null($deny_cid)) {
+ $deny_cid = trim($deny_cid);
+ $sql_extra .= (($sql_extra != "") ? " ," : "") . "`deny_cid` = '$deny_cid'";
+ }
+
+ if (!is_null($allow_gid)) {
+ $allow_gid = trim($allow_gid);
+ $sql_extra .= (($sql_extra != "") ? " ," : "") . "`allow_gid` = '$allow_gid'";
+ }
+
+ if (!is_null($deny_gid)) {
+ $deny_gid = trim($deny_gid);
+ $sql_extra .= (($sql_extra != "") ? " ," : "") . "`deny_gid` = '$deny_gid'";
+ }
+
+ $result = false;
+ if ($sql_extra != "") {
+ $nothingtodo = false;
+ $result = q("UPDATE `photo` SET %s, `edited`='%s' WHERE `uid` = %d AND `resource-id` = '%s' AND `album` = '%s'",
+ $sql_extra,
+ datetime_convert(), // update edited timestamp
+ intval(api_user()),
+ dbesc($photo_id),
+ dbesc($album));
+ } else {
+ $nothingtodo = true;
+ }
+
+ if (x($_FILES,'media')) {
+ $nothingtodo = false;
+ $media = $_FILES['media'];
+ $data = save_media_to_database("photo", $media, $type, $album, $allow_cid, $deny_cid, $allow_gid, $deny_gid, $desc, 0, $visibility, $photo_id);
+ if (!is_null($data)) {
+ return api_format_data("photo_update", $type, $data);
+ }
+ }
+
+ // return success of updating or error message
+ if ($result) {
+ $answer = array('result' => 'updated', 'message' => 'Image id `' . $photo_id . '` has been updated.');
+ return api_format_data("photo_update", $type, array('$result' => $answer));
+ } else {
+ if ($nothingtodo) {
+ $answer = array('result' => 'cancelled', 'message' => 'Nothing to update for image id `' . $photo_id . '`.');
+ return api_format_data("photo_update", $type, array('$result' => $answer));
+ }
+ throw new InternalServerErrorException("unknown error - update photo entry in database failed");
+ }
+ }
+ throw new InternalServerErrorException("unknown error - this error on uploading or updating a photo should never happen");
+ }
+
+
+ /**
+ * @brief delete a single photo from the database through api
+ *
+ * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
+ * @return string
+ */
+ function api_fr_photo_delete($type) {
+ if (api_user() === false) {
+ throw new ForbiddenException();
+ }
+ // input params
+ $photo_id = (x($_REQUEST, 'photo_id') ? $_REQUEST['photo_id'] : null);
+
+ // do several checks on input parameters
+ // we do not allow calls without photo id
+ if ($photo_id == null) {
+ throw new BadRequestException("no photo_id specified");
+ }
+ // check if photo is existing in database
+ $r = q("SELECT `id` FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s'",
+ intval(api_user()),
+ dbesc($photo_id)
+ );
+ if (!dbm::is_result($r)) {
+ throw new BadRequestException("photo not available");
+ }
+ // now we can perform on the deletion of the photo
+ $result = q("DELETE FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s'",
+ intval(api_user()),
+ dbesc($photo_id));
+
+ // return success of deletion or error message
+ if ($result) {
+ // retrieve the id of the parent element (the photo element)
+ $photo_item = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `resource-id` = '%s' AND `type` = 'photo'",
+ intval(local_user()),
+ dbesc($photo_id)
+ );
+
+ if (!dbm::is_result($photo_item)) {
+ throw new InternalServerErrorException("problem with deleting items occured");
+ }
+ // function for setting the items to "deleted = 1" which ensures that comments, likes etc. are not shown anymore
+ // to the user and the contacts of the users (drop_items() do all the necessary magic to avoid orphans in database and federate deletion)
+ drop_item($photo_item[0]['id'], false);
+
+ $answer = array('result' => 'deleted', 'message' => 'photo with id `' . $photo_id . '` has been deleted from server.');
+ return api_format_data("photo_delete", $type, array('$result' => $answer));
+ } else {
+ throw new InternalServerErrorException("unknown error on deleting photo from database table");
+ }
+ }
+
+
+ /**
+ * @brief returns the details of a specified photo id, if scale is given, returns the photo data in base 64
+ *
+ * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
+ * @return string
+ */
function api_fr_photo_detail($type) {
if (api_user() === false) {
throw new ForbiddenException();
- } elseif (!x($_REQUEST, 'photo_id')) {
+ }
+ if (!x($_REQUEST, 'photo_id')) {
throw new BadRequestException("No photo id.");
}
$scale = (x($_REQUEST, 'scale') ? intval($_REQUEST['scale']) : false);
- $scale_sql = ($scale === false ? "" : sprintf("AND `scale`=%d",intval($scale)));
- $data_sql = ($scale === false ? "" : "ANY_VALUE(`data`) AS data`,");
+ $photo_id = $_REQUEST['photo_id'];
- $r = q("SELECT %s ANY_VALUE(`resource-id`) AS `resource-id`, ANY_VALUE(`created`) AS `created`,
- ANY_VALUE(`edited`) AS `edited`, ANY_VALUE(`title`) AS `title`, ANY_VALUE(`desc`) AS `desc`,
- ANY_VALUE(`album`) AS `album`, ANY_VALUE(`filename`) AS `filename`, ANY_VALUE(`type`) AS `type`,
- ANY_VALUE(`height`) AS `height`, ANY_VALUE(`width`) AS `width`, ANY_VALUE(`datasize`) AS `datasize`,
- ANY_VALUE(`profile`) AS `profile`, min(`scale`) as minscale, max(`scale`) as maxscale
- FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s' %s",
+ // prepare json/xml output with data from database for the requested photo
+ $data = prepare_photo_data($type, $scale, $photo_id);
+
+ return api_format_data("photo_detail", $type, $data);
+ }
+
+
+ /**
+ * @brief updates the profile image for the user (either a specified profile or the default profile)
+ *
+ * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
+ * @return string
+ */
+ function api_account_update_profile_image($type) {
+ if (api_user() === false) {
+ throw new ForbiddenException();
+ }
+ // input params
+ $profileid = (x($_REQUEST, 'profile_id') ? $_REQUEST['profile_id'] : 0);
+
+ // error if image data is missing
+ if (!x($_FILES, 'image')) {
+ throw new BadRequestException("no media data submitted");
+ }
+
+ // check if specified profile id is valid
+ if ($profileid != 0) {
+ $r = q("SELECT `id` FROM `profile` WHERE `uid` = %d AND `id` = %d",
+ intval(api_user()),
+ intval($profileid));
+ // error message if specified profile id is not in database
+ if (!dbm::is_result($r)) {
+ throw new BadRequestException("profile_id not available");
+ }
+ $is_default_profile = $r['profile'];
+ } else {
+ $is_default_profile = 1;
+ }
+
+ // get mediadata from image or media (Twitter call api/account/update_profile_image provides image)
+ $media = null;
+ if (x($_FILES, 'image')) {
+ $media = $_FILES['image'];
+ } elseif (x($_FILES, 'media')) {
+ $media = $_FILES['media'];
+ }
+ // save new profile image
+ $data = save_media_to_database("profileimage", $media, $type, t('Profile Photos'), "", "", "", "", "", $is_default_profile);
+
+ // get filetype
+ if (is_array($media['type'])) {
+ $filetype = $media['type'][0];
+ } else {
+ $filetype = $media['type'];
+ }
+ if ($filetype == "image/jpeg") {
+ $fileext = "jpg";
+ } elseif ($filetype == "image/png") {
+ $fileext = "png";
+ }
+ // change specified profile or all profiles to the new resource-id
+ if ($is_default_profile) {
+ $r = q("UPDATE `photo` SET `profile` = 0 WHERE `profile` = 1 AND `resource-id` != '%s' AND `uid` = %d",
+ dbesc($data['photo']['id']),
+ intval(local_user())
+ );
+
+ $r = q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s' WHERE `self` AND `uid` = %d",
+ dbesc(App::get_baseurl() . '/photo/' . $data['photo']['id'] . '-4.' . $fileext),
+ dbesc(App::get_baseurl() . '/photo/' . $data['photo']['id'] . '-5.' . $fileext),
+ dbesc(App::get_baseurl() . '/photo/' . $data['photo']['id'] . '-6.' . $fileext),
+ intval(local_user())
+ );
+ } else {
+ $r = q("UPDATE `profile` SET `photo` = '%s', `thumb` = '%s' WHERE `id` = %d AND `uid` = %d",
+ dbesc(App::get_baseurl() . '/photo/' . $data['photo']['id'] . '-4.' . $filetype),
+ dbesc(App::get_baseurl() . '/photo/' . $data['photo']['id'] . '-5.' . $filetype),
+ intval($_REQUEST['profile']),
+ intval(local_user())
+ );
+ }
+
+ // we'll set the updated profile-photo timestamp even if it isn't the default profile,
+ // so that browsers will do a cache update unconditionally
+
+ $r = q("UPDATE `contact` SET `avatar-date` = '%s' WHERE `self` = 1 AND `uid` = %d",
+ dbesc(datetime_convert()),
+ intval(local_user())
+ );
+
+ // Update global directory in background
+ //$user = api_get_user(get_app());
+ $url = App::get_baseurl() . '/profile/' . get_app()->user['nickname'];
+ if ($url && strlen(get_config('system', 'directory'))) {
+ proc_run(PRIORITY_LOW, "include/directory.php", $url);
+ }
+
+ require_once 'include/profile_update.php';
+ profile_change();
+
+ // output for client
+ if ($data) {
+ return api_account_verify_credentials($type);
+ } else {
+ // SaveMediaToDatabase failed for some reason
+ throw new InternalServerErrorException("image upload failed");
+ }
+ }
+
+ // place api-register for photoalbum calls before 'api/friendica/photo', otherwise this function is never reached
+ api_register_func('api/friendica/photoalbum/delete', 'api_fr_photoalbum_delete', true, API_METHOD_DELETE);
+ api_register_func('api/friendica/photoalbum/update', 'api_fr_photoalbum_update', true, API_METHOD_POST);
+ api_register_func('api/friendica/photos/list', 'api_fr_photos_list', true);
+ api_register_func('api/friendica/photo/create', 'api_fr_photo_create_update', true, API_METHOD_POST);
+ api_register_func('api/friendica/photo/update', 'api_fr_photo_create_update', true, API_METHOD_POST);
+ api_register_func('api/friendica/photo/delete', 'api_fr_photo_delete', true, API_METHOD_DELETE);
+ api_register_func('api/friendica/photo', 'api_fr_photo_detail', true);
+ api_register_func('api/account/update_profile_image', 'api_account_update_profile_image', true, API_METHOD_POST);
+
+
+ function check_acl_input($acl_string) {
+ if ($acl_string == null || $acl_string == " ") {
+ return false;
+ }
+ $contact_not_found = false;
+
+ // split into array of cid's
+ preg_match_all("/<[A-Za-z0-9]+>/", $acl_string, $array);
+
+ // check for each cid if it is available on server
+ $cid_array = $array[0];
+ foreach ($cid_array as $cid) {
+ $cid = str_replace("<", "", $cid);
+ $cid = str_replace(">", "", $cid);
+ $contact = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d",
+ intval($cid),
+ intval(api_user()));
+ $contact_not_found |= !dbm::is_result($contact);
+ }
+ return $contact_not_found;
+ }
+
+ function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $deny_cid, $allow_gid, $deny_gid, $desc, $profile = 0, $visibility = false, $photo_id = null) {
+ $visitor = 0;
+ $src = "";
+ $filetype = "";
+ $filename = "";
+ $filesize = 0;
+
+ if (is_array($media)) {
+ if (is_array($media['tmp_name'])) {
+ $src = $media['tmp_name'][0];
+ } else {
+ $src = $media['tmp_name'];
+ }
+ if (is_array($media['name'])) {
+ $filename = basename($media['name'][0]);
+ } else {
+ $filename = basename($media['name']);
+ }
+ if (is_array($media['size'])) {
+ $filesize = intval($media['size'][0]);
+ } else {
+ $filesize = intval($media['size']);
+ }
+ if (is_array($media['type'])) {
+ $filetype = $media['type'][0];
+ } else {
+ $filetype = $media['type'];
+ }
+ }
+
+ if ($filetype == "") {
+ $filetype=guess_image_type($filename);
+ }
+ $imagedata = getimagesize($src);
+ if ($imagedata) {
+ $filetype = $imagedata['mime'];
+ }
+ logger("File upload src: " . $src . " - filename: " . $filename .
+ " - size: " . $filesize . " - type: " . $filetype, LOGGER_DEBUG);
+
+ // check if there was a php upload error
+ if ($filesize == 0 && $media['error'] == 1) {
+ throw new InternalServerErrorException("image size exceeds PHP config settings, file was rejected by server");
+ }
+ // check against max upload size within Friendica instance
+ $maximagesize = get_config('system', 'maximagesize');
+ if (($maximagesize) && ($filesize > $maximagesize)) {
+ $formattedBytes = formatBytes($maximagesize);
+ throw new InternalServerErrorException("image size exceeds Friendica config setting (uploaded size: $formattedBytes)");
+ }
+
+ // create Photo instance with the data of the image
+ $imagedata = @file_get_contents($src);
+ $ph = new Photo($imagedata, $filetype);
+ if (! $ph->is_valid()) {
+ throw new InternalServerErrorException("unable to process image data");
+ }
+
+ // check orientation of image
+ $ph->orient($src);
+ @unlink($src);
+
+ // check max length of images on server
+ $max_length = get_config('system', 'max_image_length');
+ if (! $max_length) {
+ $max_length = MAX_IMAGE_LENGTH;
+ }
+ if ($max_length > 0) {
+ $ph->scaleImage($max_length);
+ logger("File upload: Scaling picture to new size " . $max_length, LOGGER_DEBUG);
+ }
+ $width = $ph->getWidth();
+ $height = $ph->getHeight();
+
+ // create a new resource-id if not already provided
+ $hash = ($photo_id == null) ? photo_new_resource() : $photo_id;
+
+ if ($mediatype == "photo") {
+ // upload normal image (scales 0, 1, 2)
+ logger("photo upload: starting new photo upload", LOGGER_DEBUG);
+
+ $r =$ph->store(local_user(), $visitor, $hash, $filename, $album, 0, 0, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
+ if (! $r) {
+ logger("photo upload: image upload with scale 0 (original size) failed");
+ }
+ if($width > 640 || $height > 640) {
+ $ph->scaleImage(640);
+ $r = $ph->store(local_user(),$visitor, $hash, $filename, $album, 1, 0, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
+ if (! $r) {
+ logger("photo upload: image upload with scale 1 (640x640) failed");
+ }
+ }
+
+ if ($width > 320 || $height > 320) {
+ $ph->scaleImage(320);
+ $r = $ph->store(local_user(), $visitor, $hash, $filename, $album, 2, 0, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
+ if (! $r) {
+ logger("photo upload: image upload with scale 2 (320x320) failed");
+ }
+ }
+ logger("photo upload: new photo upload ended", LOGGER_DEBUG);
+ } elseif ($mediatype == "profileimage") {
+ // upload profile image (scales 4, 5, 6)
+ logger("photo upload: starting new profile image upload", LOGGER_DEBUG);
+
+ if ($width > 175 || $height > 175) {
+ $ph->scaleImage(175);
+ $r = $ph->store(local_user(),$visitor, $hash, $filename, $album, 4, $profile, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
+ if (! $r) {
+ logger("photo upload: profile image upload with scale 4 (175x175) failed");
+ }
+ }
+
+ if ($width > 80 || $height > 80) {
+ $ph->scaleImage(80);
+ $r = $ph->store(local_user(),$visitor, $hash, $filename, $album, 5, $profile, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
+ if (! $r) {
+ logger("photo upload: profile image upload with scale 5 (80x80) failed");
+ }
+ }
+
+ if ($width > 48 || $height > 48) {
+ $ph->scaleImage(48);
+ $r = $ph->store(local_user(), $visitor, $hash, $filename, $album, 6, $profile, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
+ if (! $r) {
+ logger("photo upload: profile image upload with scale 6 (48x48) failed");
+ }
+ }
+ $ph->__destruct();
+ logger("photo upload: new profile image upload ended", LOGGER_DEBUG);
+ }
+
+ if ($r) {
+ // create entry in 'item'-table on new uploads to enable users to comment/like/dislike the photo
+ if ($photo_id == null && $mediatype == "photo") {
+ post_photo_item($hash, $allow_cid, $deny_cid, $allow_gid, $deny_gid, $filetype, $visibility);
+ }
+ // on success return image data in json/xml format (like /api/friendica/photo does when no scale is given)
+ return prepare_photo_data($type, false, $hash);
+ } else {
+ throw new InternalServerErrorException("image upload failed");
+ }
+ }
+
+ function post_photo_item($hash, $allow_cid, $deny_cid, $allow_gid, $deny_gid, $filetype, $visibility = false) {
+ // get data about the api authenticated user
+ $uri = item_new_uri(get_app()->get_hostname(), intval(api_user()));
+ $owner_record = q("SELECT * FROM `contact` WHERE `uid`= %d AND `self` LIMIT 1", intval(api_user()));
+
+ $arr = array();
+ $arr['guid'] = get_guid(32);
+ $arr['uid'] = intval(api_user());
+ $arr['uri'] = $uri;
+ $arr['parent-uri'] = $uri;
+ $arr['type'] = 'photo';
+ $arr['wall'] = 1;
+ $arr['resource-id'] = $hash;
+ $arr['contact-id'] = $owner_record[0]['id'];
+ $arr['owner-name'] = $owner_record[0]['name'];
+ $arr['owner-link'] = $owner_record[0]['url'];
+ $arr['owner-avatar'] = $owner_record[0]['thumb'];
+ $arr['author-name'] = $owner_record[0]['name'];
+ $arr['author-link'] = $owner_record[0]['url'];
+ $arr['author-avatar'] = $owner_record[0]['thumb'];
+ $arr['title'] = "";
+ $arr['allow_cid'] = $allow_cid;
+ $arr['allow_gid'] = $allow_gid;
+ $arr['deny_cid'] = $deny_cid;
+ $arr['deny_gid'] = $deny_gid;
+ $arr['last-child'] = 1;
+ $arr['visible'] = $visibility;
+ $arr['origin'] = 1;
+
+ $typetoext = array(
+ 'image/jpeg' => 'jpg',
+ 'image/png' => 'png',
+ 'image/gif' => 'gif'
+ );
+
+ // adds link to the thumbnail scale photo
+ $arr['body'] = '[url=' . App::get_baseurl() . '/photos/' . $owner_record[0]['name'] . '/image/' . $hash . ']'
+ . '[img]' . App::get_baseurl() . '/photo/' . $hash . '-' . "2" . '.'. $typetoext[$filetype] . '[/img]'
+ . '[/url]';
+
+ // do the magic for storing the item in the database and trigger the federation to other contacts
+ item_store($arr);
+ }
+
+ function prepare_photo_data($type, $scale, $photo_id) {
+ $scale_sql = ($scale === false ? "" : sprintf("and scale=%d", intval($scale)));
+ $data_sql = ($scale === false ? "" : "data, ");
+
+ // added allow_cid, allow_gid, deny_cid, deny_gid to output as string like stored in database
+ // clients needs to convert this in their way for further processing
+ $r = q("SELECT %s `resource-id`, `created`, `edited`, `title`, `desc`, `album`, `filename`,
+ `type`, `height`, `width`, `datasize`, `profile`, `allow_cid`, `deny_cid`, `allow_gid`, `deny_gid`,
+ MIN(`scale`) AS `minscale`, MAX(`scale`) AS `maxscale`
+ FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s' %s GROUP BY `resource-id`",
$data_sql,
intval(local_user()),
- dbesc($_REQUEST['photo_id']),
+ dbesc($photo_id),
$scale_sql
);
@@ -3341,6 +3995,7 @@ $called_api = null;
'image/gif' => 'gif'
);
+ // prepare output data for photo
if (dbm::is_result($r)) {
$data = array('photo' => $r[0]);
$data['photo']['id'] = $data['photo']['resource-id'];
@@ -3353,13 +4008,16 @@ $called_api = null;
$data['photo']['links'] = array();
for ($k = intval($data['photo']['minscale']); $k <= intval($data['photo']['maxscale']); $k++) {
$data['photo']['links'][$k . ":link"]["@attributes"] = array("type" => $data['photo']['type'],
- "scale" => $k,
- "href" => App::get_baseurl() . "/photo/" . $data['photo']['resource-id'] . "-" . $k . "." . $typetoext[$data['photo']['type']]);
+ "scale" => $k,
+ "href" => App::get_baseurl() . "/photo/" . $data['photo']['resource-id'] . "-" . $k . "." . $typetoext[$data['photo']['type']]);
}
} else {
$data['photo']['link'] = array();
+ // when we have profile images we could have only scales from 4 to 6, but index of array always needs to start with 0
+ $i = 0;
for ($k = intval($data['photo']['minscale']); $k <= intval($data['photo']['maxscale']); $k++) {
- $data['photo']['link'][$k] = App::get_baseurl() . "/photo/" . $data['photo']['resource-id'] . "-" . $k . "." . $typetoext[$data['photo']['type']];
+ $data['photo']['link'][$i] = App::get_baseurl() . "/photo/" . $data['photo']['resource-id'] . "-" . $k . "." . $typetoext[$data['photo']['type']];
+ $i++;
}
}
unset($data['photo']['resource-id']);
@@ -3370,13 +4028,54 @@ $called_api = null;
throw new NotFoundException();
}
- return api_format_data("photo_detail", $type, $data);
+ // retrieve item element for getting activities (like, dislike etc.) related to photo
+ $item = q("SELECT * FROM `item` WHERE `uid` = %d AND `resource-id` = '%s' AND `type` = 'photo'",
+ intval(local_user()),
+ dbesc($photo_id)
+ );
+ $data['photo']['friendica_activities'] = api_format_items_activities($item[0], $type);
+
+ // retrieve comments on photo
+ $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`,
+ `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`,
+ `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
+ `contact`.`id` AS `cid`
+ FROM `item`
+ STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid`
+ 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' OR `type`='photo')",
+ intval($item[0]['parent']),
+ intval(api_user()),
+ dbesc(ACTIVITY_POST)
+ );
+
+ // prepare output of comments
+ $commentData = api_format_items($r, api_get_user(get_app()), false, $type);
+ $comments = array();
+ if ($type == "xml") {
+ $k = 0;
+ foreach ($commentData as $comment) {
+ $comments[$k++ . ":comment"] = $comment;
+ }
+ } else {
+ foreach ($commentData as $comment) {
+ $comments[] = $comment;
+ }
+ }
+ $data['photo']['friendica_comments'] = $comments;
+
+ // include info if rights on photo and rights on item are mismatching
+ $rights_mismatch = $data['photo']['allow_cid'] != $item[0]['allow_cid'] ||
+ $data['photo']['deny_cid'] != $item[0]['deny_cid'] ||
+ $data['photo']['allow_gid'] != $item[0]['allow_gid'] ||
+ $data['photo']['deny_cid'] != $item[0]['deny_cid'];
+ $data['photo']['rights_mismatch'] = $rights_mismatch;
+
+ return $data;
}
- api_register_func('api/friendica/photos/list', 'api_fr_photos_list', true);
- api_register_func('api/friendica/photo', 'api_fr_photo_detail', true);
-
-
/**
* similar as /mod/redir.php
@@ -4299,7 +4998,6 @@ friendships/exists
friendships/show
account/update_location
account/update_profile_background_image
-account/update_profile_image
blocks/create
blocks/destroy
friendica/profile/update
diff --git a/include/auth.php b/include/auth.php
index c1a6120db..5dfe9d3e0 100644
--- a/include/auth.php
+++ b/include/auth.php
@@ -1,6 +1,7 @@
rLogFile);
}
}
-?>
diff --git a/include/bb2diaspora.php b/include/bb2diaspora.php
index a07a58353..80ee1b50d 100644
--- a/include/bb2diaspora.php
+++ b/include/bb2diaspora.php
@@ -1,12 +1,15 @@
0.9)) {
- $title2 = $data["url"];
- }
- $text = sprintf('%s
',
- $data["url"], $data["title"], $title2);
+ $text = style_url_for_mastodon($data["url"]);
} elseif (($simplehtml != 4) AND ($simplehtml != 0)) {
$text = sprintf('%s
', $data["url"], $data["title"]);
} else {
@@ -168,6 +159,54 @@ function cleancss($input) {
return $cleaned;
}
+/**
+ * @brief Converts [url] BBCodes in a format that looks fine on Mastodon. (callback function)
+ * @param array $match Array with the matching values
+ * @return string reformatted link including HTML codes
+ */
+function bb_style_url($match) {
+ $url = $match[1];
+
+ if (isset($match[2]) AND ($match[1] != $match[2])) {
+ return $match[0];
+ }
+
+ $parts = parse_url($url);
+ if (!isset($parts['scheme'])) {
+ return $match[0];
+ }
+
+ return style_url_for_mastodon($url);
+}
+
+/**
+ * @brief Converts [url] BBCodes in a format that looks fine on Mastodon and GNU Social.
+ * @param string $url URL that is about to be reformatted
+ * @return string reformatted link including HTML codes
+ */
+function style_url_for_mastodon($url) {
+ $styled_url = $url;
+
+ $parts = parse_url($url);
+ $scheme = $parts['scheme'].'://';
+ $styled_url = str_replace($scheme, '', $styled_url);
+
+ $html = ''.
+ '%s';
+
+ if (strlen($styled_url) > 30) {
+ $html .= '%s'.
+ '%s';
+
+ $ellipsis = substr($styled_url, 0, 30);
+ $rest = substr($styled_url, 30);
+ return sprintf($html, $url, $scheme, $ellipsis, $rest);
+ } else {
+ $html .= '%s';
+ return sprintf($html, $url, $scheme, $styled_url);
+ }
+}
+
function stripcode_br_cb($s) {
return '[code]' . str_replace('
', '', $s[1]) . '[/code]';
}
@@ -940,7 +979,14 @@ function bbcode($Text, $preserve_nl = false, $tryoembed = true, $simplehtml = fa
// if the HTML is used to generate plain text, then don't do this search, but replace all URL of that kind to text
// if ($simplehtml != 7) {
if (!$forplaintext) {
- $Text = preg_replace("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", '$1$2', $Text);
+ if ($simplehtml != 7) {
+ $Text = preg_replace("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", '$1$2', $Text);
+ } else {
+ $Text = preg_replace("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", '$1[url]$2[/url]', $Text);
+
+ $Text = preg_replace_callback("/\[url\]([$URLSearchString]*)\[\/url\]/ism", 'bb_style_url', $Text);
+ $Text = preg_replace_callback("/\[url\=([$URLSearchString]*)\]([$URLSearchString]*)\[\/url\]/ism", 'bb_style_url', $Text);
+ }
} else {
$Text = preg_replace("(\[url\]([$URLSearchString]*)\[\/url\])ism", " $1 ", $Text);
$Text = preg_replace_callback("&\[url=([^\[\]]*)\]\[img\](.*)\[\/img\]\[\/url\]&Usi", 'bb_RemovePictureLinks', $Text);
diff --git a/include/cache.php b/include/cache.php
index 98799bcf4..b56906c54 100644
--- a/include/cache.php
+++ b/include/cache.php
@@ -5,8 +5,8 @@
* @brief Class for storing data for a short time
*/
-use \Friendica\Core\Config;
-use \Friendica\Core\PConfig;
+use Friendica\Core\Config;
+use Friendica\Core\PConfig;
class Cache {
/**
@@ -121,9 +121,9 @@ class Cache {
/**
* @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
diff --git a/include/cli_startup.php b/include/cli_startup.php
index 557d08c28..01ee90f42 100644
--- a/include/cli_startup.php
+++ b/include/cli_startup.php
@@ -1,6 +1,7 @@
diff --git a/include/cron.php b/include/cron.php
index c4299dd4c..bac9c8a3d 100644
--- a/include/cron.php
+++ b/include/cron.php
@@ -1,5 +1,6 @@
1) {
+ $port = trim($serverdata[1]);
+ }
+
$server = trim($server);
$user = trim($user);
$pass = trim($pass);
@@ -52,6 +64,11 @@ class dba {
if (class_exists('\PDO') && in_array('mysql', PDO::getAvailableDrivers())) {
$this->driver = 'pdo';
$connect = "mysql:host=".$server.";dbname=".$db;
+
+ if (isset($port)) {
+ $connect .= ";port=".$port;
+ }
+
if (isset($a->config["system"]["db_charset"])) {
$connect .= ";charset=".$a->config["system"]["db_charset"];
}
@@ -61,7 +78,7 @@ class dba {
}
} elseif (class_exists('mysqli')) {
$this->driver = 'mysqli';
- $this->db = @new mysqli($server,$user,$pass,$db);
+ $this->db = @new mysqli($server, $user, $pass, $db, $port);
if (!mysqli_connect_errno()) {
$this->connected = true;
@@ -71,8 +88,8 @@ class dba {
}
} elseif (function_exists('mysql_connect')) {
$this->driver = 'mysql';
- $this->db = mysql_connect($server,$user,$pass);
- if ($this->db && mysql_select_db($db,$this->db)) {
+ $this->db = mysql_connect($serveraddr, $user, $pass);
+ if ($this->db && mysql_select_db($db, $this->db)) {
$this->connected = true;
if (isset($a->config["system"]["db_charset"])) {
@@ -93,6 +110,8 @@ class dba {
}
}
$a->save_timestamp($stamp1, "network");
+
+ self::$dbo = $this;
}
/**
@@ -131,30 +150,6 @@ class dba {
return $r[0]['db'];
}
- /**
- * @brief Returns the number of rows
- *
- * @return integer
- */
- public function num_rows() {
- if (!$this->result) {
- return 0;
- }
-
- switch ($this->driver) {
- case 'pdo':
- $rows = $this->result->rowCount();
- break;
- case 'mysqli':
- $rows = $this->result->num_rows;
- break;
- case 'mysql':
- $rows = mysql_num_rows($this->result);
- break;
- }
- return $rows;
- }
-
/**
* @brief Analyze a database query and log this if some conditions are met.
*
@@ -249,7 +244,7 @@ class dba {
break;
}
$stamp2 = microtime(true);
- $duration = (float)($stamp2-$stamp1);
+ $duration = (float)($stamp2 - $stamp1);
$a->save_timestamp($stamp1, "database");
@@ -379,41 +374,6 @@ class dba {
return($r);
}
- public function qfetch() {
- $x = false;
-
- if ($this->result) {
- switch ($this->driver) {
- case 'pdo':
- $x = $this->result->fetch(PDO::FETCH_ASSOC);
- break;
- case 'mysqli':
- $x = $this->result->fetch_array(MYSQLI_ASSOC);
- break;
- case 'mysql':
- $x = mysql_fetch_array($this->result, MYSQL_ASSOC);
- break;
- }
- }
- return($x);
- }
-
- public function qclose() {
- if ($this->result) {
- switch ($this->driver) {
- case 'pdo':
- $this->result->closeCursor();
- break;
- case 'mysqli':
- $this->result->free_result();
- break;
- case 'mysql':
- mysql_free_result($this->result);
- break;
- }
- }
- }
-
public function dbg($dbg) {
$this->debug = $dbg;
}
@@ -497,6 +457,656 @@ class dba {
}
return $sql;
}
+
+ /**
+ * @brief Replaces the ? placeholders with the parameters in the $args array
+ *
+ * @param string $sql SQL query
+ * @param array $args The parameters that are to replace the ? placeholders
+ * @return string The replaced SQL query
+ */
+ static private function replace_parameters($sql, $args) {
+ $offset = 0;
+ foreach ($args AS $param => $value) {
+ if (is_int($args[$param]) OR is_float($args[$param])) {
+ $replace = intval($args[$param]);
+ } else {
+ $replace = "'".self::$dbo->escape($args[$param])."'";
+ }
+
+ $pos = strpos($sql, '?', $offset);
+ if ($pos !== false) {
+ $sql = substr_replace($sql, $replace, $pos, 1);
+ }
+ $offset = $pos + strlen($replace);
+ }
+ return $sql;
+ }
+
+ /**
+ * @brief Executes a prepared statement that returns data
+ * @usage Example: $r = p("SELECT * FROM `item` WHERE `guid` = ?", $guid);
+ * @param string $sql SQL statement
+ * @return object statement object
+ */
+ static public function p($sql) {
+ $a = get_app();
+
+ $stamp1 = microtime(true);
+
+ $args = func_get_args();
+ unset($args[0]);
+
+ // When the second function parameter is an array then use this as the parameter array
+ if ((count($args) > 0) AND (is_array($args[1]))) {
+ $params = $args[1];
+ } else {
+ $params = $args;
+ }
+
+ // Renumber the array keys to be sure that they fit
+ $i = 0;
+ $args = array();
+ foreach ($params AS $param) {
+ $args[++$i] = $param;
+ }
+
+ if (!self::$dbo OR !self::$dbo->connected) {
+ return false;
+ }
+
+ if (substr_count($sql, '?') != count($args)) {
+ // Question: Should we continue or stop the query here?
+ logger('Parameter mismatch. Query "'.$sql.'" - Parameters '.print_r($args, true), LOGGER_DEBUG);
+ }
+
+ $sql = self::$dbo->any_value_fallback($sql);
+
+ if (x($a->config,'system') && x($a->config['system'], 'db_callstack')) {
+ $sql = "/*".$a->callstack()." */ ".$sql;
+ }
+
+ self::$dbo->error = '';
+ self::$dbo->errorno = 0;
+
+ switch (self::$dbo->driver) {
+ case 'pdo':
+ if (!$stmt = self::$dbo->db->prepare($sql)) {
+ $errorInfo = self::$dbo->db->errorInfo();
+ self::$dbo->error = $errorInfo[2];
+ self::$dbo->errorno = $errorInfo[1];
+ $retval = false;
+ break;
+ }
+
+ foreach ($args AS $param => $value) {
+ $stmt->bindParam($param, $args[$param]);
+ }
+
+ if (!$stmt->execute()) {
+ $errorInfo = $stmt->errorInfo();
+ self::$dbo->error = $errorInfo[2];
+ self::$dbo->errorno = $errorInfo[1];
+ $retval = false;
+ } else {
+ $retval = $stmt;
+ }
+ break;
+ case 'mysqli':
+ $stmt = self::$dbo->db->stmt_init();
+
+ if (!$stmt->prepare($sql)) {
+ self::$dbo->error = $stmt->error;
+ self::$dbo->errorno = $stmt->errno;
+ $retval = false;
+ break;
+ }
+
+ $params = '';
+ $values = array();
+ foreach ($args AS $param => $value) {
+ if (is_int($args[$param])) {
+ $params .= 'i';
+ } elseif (is_float($args[$param])) {
+ $params .= 'd';
+ } elseif (is_string($args[$param])) {
+ $params .= 's';
+ } else {
+ $params .= 'b';
+ }
+ $values[] = &$args[$param];
+ }
+
+ if (count($values) > 0) {
+ array_unshift($values, $params);
+ call_user_func_array(array($stmt, 'bind_param'), $values);
+ }
+
+ if (!$stmt->execute()) {
+ self::$dbo->error = self::$dbo->db->error;
+ self::$dbo->errorno = self::$dbo->db->errno;
+ $retval = false;
+ } else {
+ $stmt->store_result();
+ $retval = $stmt;
+ }
+ break;
+ case 'mysql':
+ // For the old "mysql" functions we cannot use prepared statements
+ $retval = mysql_query(self::replace_parameters($sql, $args), self::$dbo->db);
+ if (mysql_errno(self::$dbo->db)) {
+ self::$dbo->error = mysql_error(self::$dbo->db);
+ self::$dbo->errorno = mysql_errno(self::$dbo->db);
+ }
+ break;
+ }
+
+ if (self::$dbo->errorno != 0) {
+ logger('DB Error '.self::$dbo->errorno.': '.self::$dbo->error."\n".self::replace_parameters($sql, $args));
+ }
+
+ $a->save_timestamp($stamp1, 'database');
+
+ if (x($a->config,'system') && x($a->config['system'], 'db_log')) {
+
+ $stamp2 = microtime(true);
+ $duration = (float)($stamp2 - $stamp1);
+
+ if (($duration > $a->config["system"]["db_loglimit"])) {
+ $duration = round($duration, 3);
+ $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+
+ @file_put_contents($a->config["system"]["db_log"], datetime_convert()."\t".$duration."\t".
+ basename($backtrace[1]["file"])."\t".
+ $backtrace[1]["line"]."\t".$backtrace[2]["function"]."\t".
+ substr(self::replace_parameters($sql, $args), 0, 2000)."\n", FILE_APPEND);
+ }
+ }
+ return $retval;
+ }
+
+ /**
+ * @brief Executes a prepared statement like UPDATE or INSERT that doesn't return data
+ *
+ * @param string $sql SQL statement
+ * @return boolean Was the query successfull? False is returned only if an error occurred
+ */
+ static public function e($sql) {
+ $a = get_app();
+
+ $stamp = microtime(true);
+
+ $args = func_get_args();
+
+ $stmt = call_user_func_array('self::p', $args);
+
+ if (is_bool($stmt)) {
+ $retval = $stmt;
+ } elseif (is_object($stmt)) {
+ $retval = true;
+ } else {
+ $retval = false;
+ }
+
+ self::close($stmt);
+
+ $a->save_timestamp($stamp, "database_write");
+
+ return $retval;
+ }
+
+ /**
+ * @brief Check if data exists
+ *
+ * @param string $sql SQL statement
+ * @return boolean Are there rows for that query?
+ */
+ static public function exists($sql) {
+ $args = func_get_args();
+
+ $stmt = call_user_func_array('self::p', $args);
+
+ if (is_bool($stmt)) {
+ $retval = $stmt;
+ } else {
+ $retval = (self::num_rows($stmt) > 0);
+ }
+
+ self::close($stmt);
+
+ return $retval;
+ }
+
+ /**
+ * @brief Fetches the first row
+ *
+ * @param string $sql SQL statement
+ * @return array first row of query
+ */
+ static public function fetch_first($sql) {
+ $args = func_get_args();
+
+ $stmt = call_user_func_array('self::p', $args);
+
+ if (is_bool($stmt)) {
+ $retval = $stmt;
+ } else {
+ $retval = self::fetch($stmt);
+ }
+
+ self::close($stmt);
+
+ return $retval;
+ }
+
+ /**
+ * @brief Returns the number of rows of a statement
+ *
+ * @param object Statement object
+ * @return int Number of rows
+ */
+ static public function num_rows($stmt) {
+ switch (self::$dbo->driver) {
+ case 'pdo':
+ return $stmt->rowCount();
+ case 'mysqli':
+ return $stmt->num_rows;
+ case 'mysql':
+ return mysql_num_rows($stmt);
+ }
+ return 0;
+ }
+
+ /**
+ * @brief Fetch a single row
+ *
+ * @param object $stmt statement object
+ * @return array current row
+ */
+ static public function fetch($stmt) {
+ if (!is_object($stmt)) {
+ return false;
+ }
+
+ switch (self::$dbo->driver) {
+ case 'pdo':
+ return $stmt->fetch(PDO::FETCH_ASSOC);
+ case 'mysqli':
+ // This code works, but is slow
+
+ // Bind the result to a result array
+ $cols = array();
+
+ $cols_num = array();
+ for ($x = 0; $x < $stmt->field_count; $x++) {
+ $cols[] = &$cols_num[$x];
+ }
+
+ call_user_func_array(array($stmt, 'bind_result'), $cols);
+
+ if (!$stmt->fetch()) {
+ return false;
+ }
+
+ // The slow part:
+ // We need to get the field names for the array keys
+ // It seems that there is no better way to do this.
+ $result = $stmt->result_metadata();
+ $fields = $result->fetch_fields();
+
+ $columns = array();
+ foreach ($cols_num AS $param => $col) {
+ $columns[$fields[$param]->name] = $col;
+ }
+ return $columns;
+ case 'mysql':
+ return mysql_fetch_array(self::$dbo->result, MYSQL_ASSOC);
+ }
+ }
+
+ /**
+ * @brief Insert a row into a table
+ *
+ * @param string $table Table name
+ * @param array $param parameter array
+ *
+ * @return boolean was the insert successfull?
+ */
+ static public function insert($table, $param) {
+ $sql = "INSERT INTO `".self::$dbo->escape($table)."` (`".implode("`, `", array_keys($param))."`) VALUES (".
+ substr(str_repeat("?, ", count($param)), 0, -2).");";
+
+ return self::e($sql, $param);
+ }
+
+ /**
+ * @brief Build the array with the table relations
+ *
+ * The array is build from the database definitions in dbstructure.php
+ *
+ * This process must only be started once, since the value is cached.
+ */
+ static private function build_relation_data() {
+ $definition = db_definition();
+
+ foreach ($definition AS $table => $structure) {
+ foreach ($structure['fields'] AS $field => $field_struct) {
+ if (isset($field_struct['relation'])) {
+ foreach ($field_struct['relation'] AS $rel_table => $rel_field) {
+ self::$relation[$rel_table][$rel_field][$table][] = $field;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @brief Insert a row into a table
+ *
+ * @param string $table Table name
+ * @param array $param parameter array
+ * @param boolean $in_commit Internal use: Only do a commit after the last delete
+ * @param array $callstack Internal use: prevent endless loops
+ *
+ * @return boolean|array was the delete successfull? When $in_commit is set: deletion data
+ */
+ static public function delete($table, $param, $in_commit = false, &$callstack = array()) {
+
+ $commands = array();
+
+ // Create a key for the loop prevention
+ $key = $table.':'.implode(':', array_keys($param)).':'.implode(':', $param);
+
+ // We quit when this key already exists in the callstack.
+ if (isset($callstack[$key])) {
+ return $commands;
+ }
+
+ $callstack[$key] = true;
+
+ $table = self::$dbo->escape($table);
+
+ $commands[$key] = array('table' => $table, 'param' => $param);
+
+ // To speed up the whole process we cache the table relations
+ if (count(self::$relation) == 0) {
+ self::build_relation_data();
+ }
+
+ // Is there a relation entry for the table?
+ if (isset(self::$relation[$table])) {
+ // We only allow a simple "one field" relation.
+ $field = array_keys(self::$relation[$table])[0];
+ $rel_def = array_values(self::$relation[$table])[0];
+
+ // Create a key for preventing double queries
+ $qkey = $field.'-'.$table.':'.implode(':', array_keys($param)).':'.implode(':', $param);
+
+ // When the search field is the relation field, we don't need to fetch the rows
+ // This is useful when the leading record is already deleted in the frontend but the rest is done in the backend
+ if ((count($param) == 1) AND ($field == array_keys($param)[0])) {
+ foreach ($rel_def AS $rel_table => $rel_fields) {
+ foreach ($rel_fields AS $rel_field) {
+ $retval = self::delete($rel_table, array($rel_field => array_values($param)[0]), true, $callstack);
+ $commands = array_merge($commands, $retval);
+ }
+ }
+ // We quit when this key already exists in the callstack.
+ } elseif (!isset($callstack[$qkey])) {
+
+ $callstack[$qkey] = true;
+
+ // Fetch all rows that are to be deleted
+ $sql = "SELECT ".self::$dbo->escape($field)." FROM `".$table."` WHERE `".
+ implode("` = ? AND `", array_keys($param))."` = ?";
+
+ $data = self::p($sql, $param);
+ while ($row = self::fetch($data)) {
+ // Now we accumulate the delete commands
+ $retval = self::delete($table, array($field => $row[$field]), true, $callstack);
+ $commands = array_merge($commands, $retval);
+ }
+
+ // Since we had split the delete command we don't need the original command anymore
+ unset($commands[$key]);
+ }
+ }
+
+ if (!$in_commit) {
+ // Now we finalize the process
+ self::p("COMMIT");
+ self::p("START TRANSACTION");
+
+ $compacted = array();
+ $counter = array();
+ foreach ($commands AS $command) {
+ if (count($command['param']) > 1) {
+ $sql = "DELETE FROM `".$command['table']."` WHERE `".
+ implode("` = ? AND `", array_keys($command['param']))."` = ?";
+
+ logger(dba::replace_parameters($sql, $command['param']), LOGGER_DATA);
+
+ if (!self::e($sql, $command['param'])) {
+ self::p("ROLLBACK");
+ return false;
+ }
+ } else {
+ $key_table = $command['table'];
+ $key_param = array_keys($command['param'])[0];
+ $value = array_values($command['param'])[0];
+
+ // Split the SQL queries in chunks of 100 values
+ // We do the $i stuff here to make the code better readable
+ $i = $counter[$key_table][$key_param];
+ if (count($compacted[$key_table][$key_param][$i]) > 100) {
+ ++$i;
+ }
+
+ $compacted[$key_table][$key_param][$i][$value] = $value;
+ $counter[$key_table][$key_param] = $i;
+ }
+ }
+ foreach ($compacted AS $table => $values) {
+ foreach ($values AS $field => $field_value_list) {
+ foreach ($field_value_list AS $field_values) {
+ $sql = "DELETE FROM `".$table."` WHERE `".$field."` IN (".
+ substr(str_repeat("?, ", count($field_values)), 0, -2).");";
+
+ logger(dba::replace_parameters($sql, $field_values), LOGGER_DATA);
+
+ if (!self::e($sql, $field_values)) {
+ self::p("ROLLBACK");
+ return false;
+ }
+ }
+ }
+ }
+ self::p("COMMIT");
+ return true;
+ }
+
+ return $commands;
+ }
+
+ /**
+ * @brief Updates rows
+ *
+ * Updates rows in the database. When $old_fields is set to an array,
+ * the system will only do an update if the fields in that array changed.
+ *
+ * Attention:
+ * Only the values in $old_fields are compared.
+ * This is an intentional behaviour.
+ *
+ * Example:
+ * We include the timestamp field in $fields but not in $old_fields.
+ * Then the row will only get the new timestamp when the other fields had changed.
+ *
+ * When $old_fields is set to a boolean value the system will do this compare itself.
+ * When $old_fields is set to "true" the system will do an insert if the row doesn't exists.
+ *
+ * Attention:
+ * Only set $old_fields to a boolean value when you are sure that you will update a single row.
+ * When you set $old_fields to "true" then $fields must contain all relevant fields!
+ *
+ * @param string $table Table name
+ * @param array $fields contains the fields that are updated
+ * @param array $condition condition array with the key values
+ * @param array|boolean $old_fields array with the old field values that are about to be replaced
+ *
+ * @return boolean was the update successfull?
+ */
+ static public function update($table, $fields, $condition, $old_fields = array()) {
+
+ /** @todo We may use MySQL specific functions here:
+ * INSERT INTO `config` (`cat`, `k`, `v`) VALUES ('%s', '%s', '%s') ON DUPLICATE KEY UPDATE `v` = '%s'"
+ * But I think that it doesn't make sense here.
+ */
+
+ $table = self::$dbo->escape($table);
+
+ if (is_bool($old_fields)) {
+ $sql = "SELECT * FROM `".$table."` WHERE `".
+ implode("` = ? AND `", array_keys($condition))."` = ? LIMIT 1";
+
+ $params = array();
+ foreach ($condition AS $value) {
+ $params[] = $value;
+ }
+
+ $do_insert = $old_fields;
+
+ $old_fields = self::fetch_first($sql, $params);
+ if (is_bool($old_fields)) {
+ if ($do_insert) {
+ return self::insert($table, $fields);
+ }
+ $old_fields = array();
+ }
+ }
+
+ $do_update = (count($old_fields) == 0);
+
+ foreach ($old_fields AS $fieldname => $content) {
+ if (isset($fields[$fieldname])) {
+ if ($fields[$fieldname] == $content) {
+ unset($fields[$fieldname]);
+ } else {
+ $do_update = true;
+ }
+ }
+ }
+
+ if (!$do_update OR (count($fields) == 0)) {
+ return true;
+ }
+
+ $sql = "UPDATE `".$table."` SET `".
+ implode("` = ?, `", array_keys($fields))."` = ? WHERE `".
+ implode("` = ? AND `", array_keys($condition))."` = ?";
+
+ $params = array();
+ foreach ($fields AS $value) {
+ $params[] = $value;
+ }
+ foreach ($condition AS $value) {
+ $params[] = $value;
+ }
+
+ return self::e($sql, $params);
+ }
+
+ /**
+ * @brief Select rows from a table
+ *
+ * @param string $table Table name
+ * @param array $fields array of selected fields
+ * @param array $condition array of fields for condition
+ * @param array $params array of several parameters
+ *
+ * @return boolean|object If "limit" is equal "1" only a single row is returned, else a query object is returned
+ *
+ * Example:
+ * $table = "item";
+ * $fields = array("id", "uri", "uid", "network");
+ * $condition = array("uid" => 1, "network" => 'dspr');
+ * $params = array("order" => array("id", "received" => true), "limit" => 1);
+ *
+ * $data = dba::select($table, $fields, $condition, $params);
+ */
+ static public function select($table, $fields = array(), $condition = array(), $params = array()) {
+ if ($table == '') {
+ return false;
+ }
+
+ if (count($fields) > 0) {
+ $select_fields = "`".implode("`, `", array_values($fields))."`";
+ } else {
+ $select_fields = "*";
+ }
+
+ if (count($condition) > 0) {
+ $condition_string = " WHERE `".implode("` = ? AND `", array_keys($condition))."` = ?";
+ } else {
+ $condition_string = "";
+ }
+
+ $param_string = '';
+ $single_row = false;
+
+ if (isset($params['order'])) {
+ $param_string .= " ORDER BY ";
+ foreach ($params['order'] AS $fields => $order) {
+ if (!is_int($fields)) {
+ $param_string .= "`".$fields."` ".($order ? "DESC" : "ASC").", ";
+ } else {
+ $param_string .= "`".$order."`, ";
+ }
+ }
+ $param_string = substr($param_string, 0, -2);
+ }
+
+ if (isset($params['limit'])) {
+ if (is_int($params['limit'])) {
+ $param_string .= " LIMIT ".$params['limit'];
+ $single_row =($params['limit'] == 1);
+ }
+ }
+
+ $sql = "SELECT ".$select_fields." FROM `".$table."`".$condition_string.$param_string;
+
+ $result = self::p($sql, $condition);
+
+ if (is_bool($result) OR !$single_row) {
+ return $result;
+ } else {
+ $row = self::fetch($result);
+ self::close($result);
+ return $row;
+ }
+ }
+
+ /**
+ * @brief Closes the current statement
+ *
+ * @param object $stmt statement object
+ * @return boolean was the close successfull?
+ */
+ static public function close($stmt) {
+ if (!is_object($stmt)) {
+ return false;
+ }
+
+ switch (self::$dbo->driver) {
+ case 'pdo':
+ return $stmt->closeCursor();
+ case 'mysqli':
+ return $stmt->free_result();
+ return $stmt->close();
+ case 'mysql':
+ return mysql_free_result($stmt);
+ }
+ }
}
function printable($s) {
diff --git a/include/dbclean.php b/include/dbclean.php
index bff4ff2a2..f31bfef8a 100644
--- a/include/dbclean.php
+++ b/include/dbclean.php
@@ -4,7 +4,7 @@
* @brief The script is called from time to time to clean the database entries and remove orphaned data.
*/
-use \Friendica\Core\Config;
+use Friendica\Core\Config;
function dbclean_run(&$argv, &$argc) {
if (!Config::get('system', 'dbclean', false)) {
@@ -43,96 +43,103 @@ function remove_orphans($stage = 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
+ $r = dba::p("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 ".intval($limit), true)) {
- $count = $db->num_rows();
+ AND `received` < UTC_TIMESTAMP() - INTERVAL 90 DAY LIMIT ".intval($limit));
+ $count = dba::num_rows($r);
+ if ($count > 0) {
logger("found global item orphans: ".$count);
- while ($orphan = $db->qfetch()) {
+ while ($orphan = dba::fetch($r)) {
q("DELETE FROM `item` WHERE `id` = %d", intval($orphan["id"]));
}
}
- $db->qclose();
+ dba::close($r);
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 ".intval($limit), true)) {
- $count = $db->num_rows();
+ $r = dba::p("SELECT `id` FROM `item` WHERE NOT EXISTS (SELECT `id` FROM `item` AS `i` WHERE `item`.`parent` = `i`.`id`) LIMIT ".intval($limit));
+ $count = dba::num_rows($r);
+ if ($count > 0) {
logger("found item orphans without parents: ".$count);
- while ($orphan = $db->qfetch()) {
+ while ($orphan = dba::fetch($r)) {
q("DELETE FROM `item` WHERE `id` = %d", intval($orphan["id"]));
}
}
- $db->qclose();
+ dba::close($r);
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`) LIMIT ".intval($limit), true)) {
- $count = $db->num_rows();
+ $r = dba::p("SELECT `iid` FROM `thread` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`parent` = `thread`.`iid`) LIMIT ".intval($limit));
+ $count = dba::num_rows($r);
+ if ($count > 0) {
logger("found thread orphans: ".$count);
- while ($orphan = $db->qfetch()) {
+ while ($orphan = dba::fetch($r)) {
q("DELETE FROM `thread` WHERE `iid` = %d", intval($orphan["iid"]));
}
}
- $db->qclose();
+ dba::close($r);
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`) LIMIT ".intval($limit), true)) {
- $count = $db->num_rows();
+ $r = dba::p("SELECT `iid` FROM `notify` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`id` = `notify`.`iid`) LIMIT ".intval($limit));
+ $count = dba::num_rows($r);
+ if ($count > 0) {
logger("found notify orphans: ".$count);
- while ($orphan = $db->qfetch()) {
+ while ($orphan = dba::fetch($r)) {
q("DELETE FROM `notify` WHERE `iid` = %d", intval($orphan["iid"]));
}
}
- $db->qclose();
+ dba::close($r);
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`) LIMIT ".intval($limit), true)) {
- $count = $db->num_rows();
+ $r = dba::p("SELECT `id` FROM `notify-threads` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`parent` = `notify-threads`.`master-parent-item`) LIMIT ".intval($limit));
+ $count = dba::num_rows($r);
+ if ($count > 0) {
logger("found notify-threads orphans: ".$count);
- while ($orphan = $db->qfetch()) {
+ while ($orphan = dba::fetch($r)) {
q("DELETE FROM `notify-threads` WHERE `id` = %d", intval($orphan["id"]));
}
}
- $db->qclose();
+ dba::close($r);
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`) LIMIT ".intval($limit), true)) {
- $count = $db->num_rows();
+ $r = dba::p("SELECT `iid` FROM `sign` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`id` = `sign`.`iid`) LIMIT ".intval($limit));
+ $count = dba::num_rows($r);
+ if ($count > 0) {
logger("found sign orphans: ".$count);
- while ($orphan = $db->qfetch()) {
+ while ($orphan = dba::fetch($r)) {
q("DELETE FROM `sign` WHERE `iid` = %d", intval($orphan["iid"]));
}
}
- $db->qclose();
+ dba::close($r);
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`) LIMIT ".intval($limit), true)) {
- $count = $db->num_rows();
+ $r = dba::p("SELECT `oid` FROM `term` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`id` = `term`.`oid`) LIMIT ".intval($limit));
+ $count = dba::num_rows($r);
+ if ($count > 0) {
logger("found term orphans: ".$count);
- while ($orphan = $db->qfetch()) {
+ while ($orphan = dba::fetch($r)) {
q("DELETE FROM `term` WHERE `oid` = %d", intval($orphan["oid"]));
}
}
- $db->qclose();
+ dba::close($r);
logger("Done deleting orphaned data from term table");
}
@@ -142,4 +149,3 @@ function remove_orphans($stage = 0) {
}
}
-?>
diff --git a/include/dbm.php b/include/dbm.php
index d28d49d63..db50dae89 100644
--- a/include/dbm.php
+++ b/include/dbm.php
@@ -2,6 +2,7 @@
/**
* @brief This class contain functions for the database management
*
+ * This class contains functions that doesn't need to know if pdo, mysqli or whatever is used.
*/
class dbm {
/**
@@ -47,6 +48,11 @@ class dbm {
if (is_bool($array)) {
return $array;
}
+
+ if (is_object($array)) {
+ return true;
+ }
+
return (is_array($array) && count($array) > 0);
}
@@ -104,4 +110,3 @@ class dbm {
return date('Y-m-d H:i:s', $timestamp);
}
}
-?>
diff --git a/include/dbstructure.php b/include/dbstructure.php
index 0d5c10f98..6a14220c2 100644
--- a/include/dbstructure.php
+++ b/include/dbstructure.php
@@ -1,19 +1,45 @@
database_name()));
+
+ if (!dbm::is_result($r)) {
+ echo t('There are no tables on MyISAM.')."\n";
+ return;
+ }
+
+ foreach ($r AS $table) {
+ $sql = sprintf("ALTER TABLE `%s` engine=InnoDB;", dbesc($table['TABLE_NAME']));
+ echo $sql."\n";
+
+ $result = @$db->q($sql);
+ if (!dbm::is_result($result)) {
+ print_update_error($db, $sql);
+ }
+ }
+}
+
/*
* send the email and do what is needed to do on update fails
*
* @param update_id (int) number of failed update
* @param error_message (str) error message
*/
-function update_fail($update_id, $error_message){
+function update_fail($update_id, $error_message) {
//send the administrators an e-mail
$admin_mail_list = "'".implode("','", array_map(dbesc, explode(",", str_replace(" ", "", $a->config['admin_email']))))."'";
$adminlist = q("SELECT uid, language, email FROM user WHERE email IN (%s)",
@@ -95,10 +121,6 @@ function table_structure($table) {
if (dbm::is_result($indexes))
foreach ($indexes AS $index) {
- 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');
}
@@ -279,6 +301,9 @@ function update_structure($verbose, $action, $tables=null, $definition=null) {
// Compare the field definition
$field_definition = $database[$name]["fields"][$fieldname];
+ // Remove the relation data that is used for the referential integrity
+ unset($parameters['relation']);
+
// We change the collation after the indexes had been changed.
// This is done to avoid index length problems.
// So here we always ensure that there is no need to change it.
@@ -374,6 +399,14 @@ function update_structure($verbose, $action, $tables=null, $definition=null) {
$sql3 .= ";";
}
+ $field_list = '';
+ if ($is_unique && $ignore == '') {
+ foreach ($structure['fields'] AS $fieldname => $parameters) {
+ $field_list .= 'ANY_VALUE(`' . $fieldname . '`),';
+ }
+ $field_list = rtrim($field_list, ',');
+ }
+
if ($verbose) {
// Ensure index conversion to unique removes duplicates
if ($is_unique) {
@@ -390,7 +423,7 @@ function update_structure($verbose, $action, $tables=null, $definition=null) {
if ($ignore != "") {
echo "SET session old_alter_table=0;\n";
} else {
- echo "INSERT INTO `".$temp_name."` SELECT * FROM `".$name."`".$group_by.";\n";
+ echo "INSERT INTO `".$temp_name."` SELECT ".$field_list." FROM `".$name."`".$group_by.";\n";
echo "DROP TABLE `".$name."`;\n";
echo "RENAME TABLE `".$temp_name."` TO `".$name."`;\n";
}
@@ -421,7 +454,7 @@ function update_structure($verbose, $action, $tables=null, $definition=null) {
if ($ignore != "") {
$db->q("SET session old_alter_table=0;");
} else {
- $r = $db->q("INSERT INTO `".$temp_name."` SELECT * FROM `".$name."`".$group_by.";");
+ $r = $db->q("INSERT INTO `".$temp_name."` SELECT ".$field_list." FROM `".$name."`".$group_by.";");
if (!dbm::is_result($r)) {
$errors .= print_update_error($db, $sql3);
return $errors;
@@ -460,7 +493,7 @@ function db_field_command($parameters, $create = true) {
if ($parameters["not null"])
$fieldstruct .= " NOT NULL";
- if (isset($parameters["default"])){
+ if (isset($parameters["default"])) {
if (strpos(strtolower($parameters["type"]),"int")!==false) {
$fieldstruct .= " DEFAULT ".$parameters["default"];
} else {
@@ -487,7 +520,7 @@ function db_create_table($name, $fields, $verbose, $action, $indexes=null) {
$primary_keys = array();
foreach ($fields AS $fieldname => $field) {
$sql_rows[] = "`".dbesc($fieldname)."` ".db_field_command($field);
- if (x($field,'primary') and $field['primary']!=''){
+ if (x($field,'primary') and $field['primary']!='') {
$primary_keys[] = $fieldname;
}
}
@@ -606,7 +639,7 @@ function db_definition() {
$database["attach"] = array(
"fields" => array(
"id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
"hash" => array("type" => "varchar(64)", "not null" => "1", "default" => ""),
"filename" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"filetype" => array("type" => "varchar(64)", "not null" => "1", "default" => ""),
@@ -626,7 +659,7 @@ function db_definition() {
$database["auth_codes"] = array(
"fields" => array(
"id" => array("type" => "varchar(40)", "not null" => "1", "primary" => "1"),
- "client_id" => array("type" => "varchar(20)", "not null" => "1", "default" => ""),
+ "client_id" => array("type" => "varchar(20)", "not null" => "1", "default" => "", "relation" => array("clients" => "client_id")),
"redirect_uri" => array("type" => "varchar(200)", "not null" => "1", "default" => ""),
"expires" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
"scope" => array("type" => "varchar(250)", "not null" => "1", "default" => ""),
@@ -667,7 +700,7 @@ function db_definition() {
"redirect_uri" => array("type" => "varchar(200)", "not null" => "1", "default" => ""),
"name" => array("type" => "text"),
"icon" => array("type" => "text"),
- "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
),
"indexes" => array(
"PRIMARY" => array("client_id"),
@@ -688,7 +721,7 @@ function db_definition() {
$database["contact"] = array(
"fields" => array(
"id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
"created" => array("type" => "datetime", "not null" => "1", "default" => NULL_DATE),
"self" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
"remote_self" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
@@ -777,7 +810,7 @@ function db_definition() {
"id" => array("type" => "int(10) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
"guid" => array("type" => "varchar(64)", "not null" => "1", "default" => ""),
"recips" => array("type" => "text"),
- "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
"creator" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"created" => array("type" => "datetime", "not null" => "1", "default" => NULL_DATE),
"updated" => array("type" => "datetime", "not null" => "1", "default" => NULL_DATE),
@@ -788,12 +821,27 @@ function db_definition() {
"uid" => array("uid"),
)
);
+ $database["conversation"] = array(
+ "fields" => array(
+ "item-uri" => array("type" => "varbinary(255)", "not null" => "1", "primary" => "1"),
+ "reply-to-uri" => array("type" => "varbinary(255)", "not null" => "1", "default" => ""),
+ "conversation-uri" => array("type" => "varbinary(255)", "not null" => "1", "default" => ""),
+ "conversation-href" => array("type" => "varbinary(255)", "not null" => "1", "default" => ""),
+ "protocol" => array("type" => "tinyint(1) unsigned", "not null" => "1", "default" => "0"),
+ "source" => array("type" => "mediumtext"),
+ "received" => array("type" => "datetime", "not null" => "1", "default" => NULL_DATE),
+ ),
+ "indexes" => array(
+ "PRIMARY" => array("item-uri"),
+ "conversation-uri" => array("conversation-uri"),
+ )
+ );
$database["event"] = array(
"fields" => array(
"id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
"guid" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
- "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
- "cid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
+ "cid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("contact" => "id")),
"uri" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"created" => array("type" => "datetime", "not null" => "1", "default" => NULL_DATE),
"edited" => array("type" => "datetime", "not null" => "1", "default" => NULL_DATE),
@@ -839,15 +887,15 @@ function db_definition() {
"indexes" => array(
"PRIMARY" => array("id"),
"addr" => array("addr(32)"),
- "url" => array("url"),
+ "url" => array("UNIQUE", "url(190)"),
)
);
$database["ffinder"] = array(
"fields" => array(
"id" => array("type" => "int(10) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
- "cid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
- "fid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
+ "cid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => array("contact" => "id")),
+ "fid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => array("fcontact" => "id")),
),
"indexes" => array(
"PRIMARY" => array("id"),
@@ -868,8 +916,8 @@ function db_definition() {
$database["fsuggest"] = array(
"fields" => array(
"id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
- "cid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
+ "cid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("contact" => "id")),
"name" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"url" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"request" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
@@ -884,8 +932,8 @@ function db_definition() {
$database["gcign"] = array(
"fields" => array(
"id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
- "gcid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
+ "gcid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("gcontact" => "id")),
),
"indexes" => array(
"PRIMARY" => array("id"),
@@ -924,7 +972,7 @@ function db_definition() {
),
"indexes" => array(
"PRIMARY" => array("id"),
- "nurl" => array("nurl(64)"),
+ "nurl" => array("UNIQUE", "nurl(190)"),
"name" => array("name(64)"),
"nick" => array("nick(32)"),
"addr" => array("addr(64)"),
@@ -935,10 +983,10 @@ function db_definition() {
$database["glink"] = array(
"fields" => array(
"id" => array("type" => "int(10) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "cid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
- "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
- "gcid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
- "zcid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "cid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("contact" => "id")),
+ "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
+ "gcid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("gcontact" => "id")),
+ "zcid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("gcontact" => "id")),
"updated" => array("type" => "datetime", "not null" => "1", "default" => NULL_DATE),
),
"indexes" => array(
@@ -950,7 +998,7 @@ function db_definition() {
$database["group"] = array(
"fields" => array(
"id" => array("type" => "int(10) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
"visible" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
"deleted" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
"name" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
@@ -963,9 +1011,9 @@ function db_definition() {
$database["group_member"] = array(
"fields" => array(
"id" => array("type" => "int(10) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
- "gid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
- "contact-id" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
+ "gid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => array("group" => "id")),
+ "contact-id" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => array("contact" => "id")),
),
"indexes" => array(
"PRIMARY" => array("id"),
@@ -994,7 +1042,7 @@ function db_definition() {
),
"indexes" => array(
"PRIMARY" => array("id"),
- "nurl" => array("nurl(32)"),
+ "nurl" => array("UNIQUE", "nurl(190)"),
)
);
$database["hook"] = array(
@@ -1013,9 +1061,9 @@ function db_definition() {
$database["intro"] = array(
"fields" => array(
"id" => array("type" => "int(10) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
- "fid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
- "contact-id" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
+ "fid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("fcontact" => "id")),
+ "contact-id" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("contact" => "id")),
"knowyou" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
"duplex" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
"note" => array("type" => "text"),
@@ -1030,16 +1078,16 @@ function db_definition() {
);
$database["item"] = array(
"fields" => array(
- "id" => array("type" => "int(10) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
+ "id" => array("type" => "int(10) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "relation" => array("thread" => "iid")),
"guid" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"uri" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
- "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
- "contact-id" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
- "gcontact-id" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
+ "contact-id" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("contact" => "id")),
+ "gcontact-id" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0", "relation" => array("gcontact" => "id")),
"type" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"wall" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
"gravity" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
- "parent" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
+ "parent" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => array("item" => "id")),
"parent-uri" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"extid" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"thr-parent" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
@@ -1048,11 +1096,11 @@ function db_definition() {
"commented" => array("type" => "datetime", "not null" => "1", "default" => NULL_DATE),
"received" => array("type" => "datetime", "not null" => "1", "default" => NULL_DATE),
"changed" => array("type" => "datetime", "not null" => "1", "default" => NULL_DATE),
- "owner-id" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "owner-id" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("contact" => "id")),
"owner-name" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"owner-link" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"owner-avatar" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
- "author-id" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "author-id" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("contact" => "id")),
"author-name" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"author-link" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"author-avatar" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
@@ -1067,7 +1115,7 @@ function db_definition() {
"postopts" => array("type" => "text"),
"plink" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"resource-id" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
- "event-id" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "event-id" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("event" => "id")),
"tag" => array("type" => "mediumtext"),
"attach" => array("type" => "mediumtext"),
"inform" => array("type" => "mediumtext"),
@@ -1113,6 +1161,7 @@ function db_definition() {
"uid_parenturi" => array("uid","parent-uri(190)"),
"uid_contactid_created" => array("uid","contact-id","created"),
"authorid_created" => array("author-id","created"),
+ "ownerid" => array("owner-id"),
"uid_uri" => array("uid", "uri(190)"),
"resource-id" => array("resource-id"),
"contactid_allowcid_allowpid_denycid_denygid" => array("contact-id","allow_cid(10)","allow_gid(10)","deny_cid(10)","deny_gid(10)"), //
@@ -1128,8 +1177,8 @@ function db_definition() {
$database["item_id"] = array(
"fields" => array(
"id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "iid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
- "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "iid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("item" => "id")),
+ "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
"sid" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"service" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
),
@@ -1155,13 +1204,13 @@ function db_definition() {
$database["mail"] = array(
"fields" => array(
"id" => array("type" => "int(10) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
"guid" => array("type" => "varchar(64)", "not null" => "1", "default" => ""),
"from-name" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"from-photo" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"from-url" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
- "contact-id" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
- "convid" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0"),
+ "contact-id" => array("type" => "varchar(255)", "not null" => "1", "default" => "", "relation" => array("contact" => "id")),
+ "convid" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0", "relation" => array("conv" => "id")),
"title" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"body" => array("type" => "mediumtext"),
"seen" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
@@ -1178,12 +1227,13 @@ function db_definition() {
"convid" => array("convid"),
"uri" => array("uri(64)"),
"parent-uri" => array("parent-uri(64)"),
+ "contactid" => array("contact-id"),
)
);
$database["mailacct"] = array(
"fields" => array(
"id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
"server" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"port" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
"ssltype" => array("type" => "varchar(16)", "not null" => "1", "default" => ""),
@@ -1203,8 +1253,8 @@ function db_definition() {
$database["manage"] = array(
"fields" => array(
"id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
- "mid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
+ "mid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
),
"indexes" => array(
"PRIMARY" => array("id"),
@@ -1221,10 +1271,10 @@ function db_definition() {
"photo" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"date" => array("type" => "datetime", "not null" => "1", "default" => NULL_DATE),
"msg" => array("type" => "mediumtext"),
- "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
"link" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
- "iid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
- "parent" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "iid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("item" => "id")),
+ "parent" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("item" => "id")),
"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" => ""),
@@ -1242,10 +1292,10 @@ function db_definition() {
$database["notify-threads"] = array(
"fields" => array(
"id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "notify-id" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
- "master-parent-item" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
+ "notify-id" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("notify" => "id")),
+ "master-parent-item" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => array("item" => "id")),
"parent-item" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
- "receiver-uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "receiver-uid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
),
"indexes" => array(
"PRIMARY" => array("id"),
@@ -1278,7 +1328,7 @@ function db_definition() {
$database["pconfig"] = array(
"fields" => array(
"id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
"cat" => array("type" => "varbinary(255)", "not null" => "1", "default" => ""),
"k" => array("type" => "varbinary(255)", "not null" => "1", "default" => ""),
"v" => array("type" => "mediumtext"),
@@ -1291,8 +1341,8 @@ function db_definition() {
$database["photo"] = array(
"fields" => array(
"id" => array("type" => "int(10) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
- "contact-id" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
+ "contact-id" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => array("contact" => "id")),
"guid" => array("type" => "varchar(64)", "not null" => "1", "default" => ""),
"resource-id" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"created" => array("type" => "datetime", "not null" => "1", "default" => NULL_DATE),
@@ -1315,6 +1365,7 @@ function db_definition() {
),
"indexes" => array(
"PRIMARY" => array("id"),
+ "contactid" => array("contact-id"),
"uid_contactid" => array("uid", "contact-id"),
"uid_profile" => array("uid", "profile"),
"uid_album_scale_created" => array("uid", "album(32)", "scale", "created"),
@@ -1325,7 +1376,7 @@ function db_definition() {
$database["poll"] = array(
"fields" => array(
"id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
"q0" => array("type" => "text"),
"q1" => array("type" => "text"),
"q2" => array("type" => "text"),
@@ -1345,7 +1396,7 @@ function db_definition() {
$database["poll_result"] = array(
"fields" => array(
"id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "poll_id" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "poll_id" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("poll" => "id")),
"choice" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
),
"indexes" => array(
@@ -1368,7 +1419,7 @@ function db_definition() {
$database["profile"] = array(
"fields" => array(
"id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
"profile-name" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"is-default" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
"hide-friends" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
@@ -1418,8 +1469,8 @@ function db_definition() {
$database["profile_check"] = array(
"fields" => array(
"id" => array("type" => "int(10) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
- "cid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
+ "cid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => array("contact" => "id")),
"dfrn_id" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"sec" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"expire" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
@@ -1431,7 +1482,7 @@ function db_definition() {
$database["push_subscriber"] = array(
"fields" => array(
"id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
"callback_url" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"topic" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"nickname" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
@@ -1446,7 +1497,7 @@ function db_definition() {
$database["queue"] = array(
"fields" => array(
"id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "cid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "cid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("contact" => "id")),
"network" => array("type" => "varchar(32)", "not null" => "1", "default" => ""),
"created" => array("type" => "datetime", "not null" => "1", "default" => NULL_DATE),
"last" => array("type" => "datetime", "not null" => "1", "default" => NULL_DATE),
@@ -1467,7 +1518,7 @@ function db_definition() {
"id" => array("type" => "int(11) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
"hash" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"created" => array("type" => "datetime", "not null" => "1", "default" => NULL_DATE),
- "uid" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
"password" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
"language" => array("type" => "varchar(16)", "not null" => "1", "default" => ""),
"note" => array("type" => "text"),
@@ -1479,7 +1530,7 @@ function db_definition() {
$database["search"] = array(
"fields" => array(
"id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
"term" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
),
"indexes" => array(
@@ -1503,7 +1554,7 @@ function db_definition() {
$database["sign"] = array(
"fields" => array(
"id" => array("type" => "int(10) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "iid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
+ "iid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => array("item" => "id")),
"signed_text" => array("type" => "mediumtext"),
"signature" => array("type" => "text"),
"signer" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
@@ -1516,7 +1567,7 @@ function db_definition() {
$database["spam"] = array(
"fields" => array(
"id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
"spam" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
"ham" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
"term" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
@@ -1533,7 +1584,7 @@ function db_definition() {
$database["term"] = array(
"fields" => array(
"tid" => array("type" => "int(10) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
- "oid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
+ "oid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => array("item" => "id")),
"otype" => array("type" => "tinyint(3) unsigned", "not null" => "1", "default" => "0"),
"type" => array("type" => "tinyint(3) unsigned", "not null" => "1", "default" => "0"),
"term" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
@@ -1543,7 +1594,7 @@ function db_definition() {
"received" => array("type" => "datetime", "not null" => "1", "default" => NULL_DATE),
"global" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
"aid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
- "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
),
"indexes" => array(
"PRIMARY" => array("tid"),
@@ -1555,12 +1606,12 @@ function db_definition() {
);
$database["thread"] = array(
"fields" => array(
- "iid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "primary" => "1"),
- "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
- "contact-id" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0"),
- "gcontact-id" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0"),
- "owner-id" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0"),
- "author-id" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0"),
+ "iid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "primary" => "1", "relation" => array("item" => "id")),
+ "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
+ "contact-id" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0", "relation" => array("contact" => "id")),
+ "gcontact-id" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0", "relation" => array("gcontact" => "id")),
+ "owner-id" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0", "relation" => array("contact" => "id")),
+ "author-id" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0", "relation" => array("contact" => "id")),
"created" => array("type" => "datetime", "not null" => "1", "default" => NULL_DATE),
"edited" => array("type" => "datetime", "not null" => "1", "default" => NULL_DATE),
"commented" => array("type" => "datetime", "not null" => "1", "default" => NULL_DATE),
@@ -1588,6 +1639,9 @@ function db_definition() {
"uid_network_created" => array("uid","network","created"),
"uid_contactid_commented" => array("uid","contact-id","commented"),
"uid_contactid_created" => array("uid","contact-id","created"),
+ "contactid" => array("contact-id"),
+ "ownerid" => array("owner-id"),
+ "authorid" => array("author-id"),
"uid_created" => array("uid","created"),
"uid_commented" => array("uid","commented"),
"uid_wall_created" => array("uid","wall","created"),
@@ -1597,10 +1651,10 @@ function db_definition() {
"fields" => array(
"id" => array("type" => "varchar(40)", "not null" => "1", "primary" => "1"),
"secret" => array("type" => "text"),
- "client_id" => array("type" => "varchar(20)", "not null" => "1", "default" => ""),
+ "client_id" => array("type" => "varchar(20)", "not null" => "1", "default" => "", "relation" => array("clients" => "client_id")),
"expires" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
"scope" => array("type" => "varchar(200)", "not null" => "1", "default" => ""),
- "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+ "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0", "relation" => array("user" => "uid")),
),
"indexes" => array(
"PRIMARY" => array("id"),
@@ -1691,8 +1745,8 @@ function db_definition() {
function dbstructure_run(&$argv, &$argc) {
global $a, $db;
- if (is_null($a)){
- $a = new App;
+ if (is_null($a)) {
+ $a = new App(dirname(__DIR__));
}
if (is_null($db)) {
@@ -1730,6 +1784,9 @@ function dbstructure_run(&$argv, &$argc) {
case "dumpsql":
print_structure(db_definition());
return;
+ case "toinnodb":
+ convert_to_innodb();
+ return;
}
}
@@ -1741,11 +1798,12 @@ function dbstructure_run(&$argv, &$argc) {
echo "dryrun show database update schema queries without running them\n";
echo "update update database schema\n";
echo "dumpsql dump database schema\n";
+ echo "toinnodb convert all tables from MyISAM to InnoDB\n";
return;
}
-if (array_search(__file__,get_included_files())===0){
+if (array_search(__file__,get_included_files())===0) {
dbstructure_run($_SERVER["argv"],$_SERVER["argc"]);
killme();
}
diff --git a/include/dbupdate.php b/include/dbupdate.php
index 725eca737..799ca262c 100644
--- a/include/dbupdate.php
+++ b/include/dbupdate.php
@@ -1,6 +1,6 @@
setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS);
$root->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET);
- xml::add_element($doc, $root, "id", app::get_baseurl()."/profile/".$owner["nick"]);
+ xml::add_element($doc, $root, "id", App::get_baseurl()."/profile/".$owner["nick"]);
xml::add_element($doc, $root, "title", $owner["name"]);
$attributes = array("uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION."-".DB_UPDATE_VERSION);
@@ -448,13 +450,13 @@ class dfrn {
// DFRN itself doesn't uses this. But maybe someone else wants to subscribe to the public feed.
ostatus::hublinks($doc, $root);
- $attributes = array("rel" => "salmon", "href" => app::get_baseurl()."/salmon/".$owner["nick"]);
+ $attributes = array("rel" => "salmon", "href" => App::get_baseurl()."/salmon/".$owner["nick"]);
xml::add_element($doc, $root, "link", "", $attributes);
- $attributes = array("rel" => "http://salmon-protocol.org/ns/salmon-replies", "href" => app::get_baseurl()."/salmon/".$owner["nick"]);
+ $attributes = array("rel" => "http://salmon-protocol.org/ns/salmon-replies", "href" => App::get_baseurl()."/salmon/".$owner["nick"]);
xml::add_element($doc, $root, "link", "", $attributes);
- $attributes = array("rel" => "http://salmon-protocol.org/ns/salmon-mention", "href" => app::get_baseurl()."/salmon/".$owner["nick"]);
+ $attributes = array("rel" => "http://salmon-protocol.org/ns/salmon-mention", "href" => App::get_baseurl()."/salmon/".$owner["nick"]);
xml::add_element($doc, $root, "link", "", $attributes);
}
@@ -511,7 +513,7 @@ class dfrn {
}
xml::add_element($doc, $author, "name", $owner["name"], $attributes);
- xml::add_element($doc, $author, "uri", app::get_baseurl().'/profile/'.$owner["nickname"], $attributes);
+ xml::add_element($doc, $author, "uri", App::get_baseurl().'/profile/'.$owner["nickname"], $attributes);
xml::add_element($doc, $author, "dfrn:handle", $owner["addr"], $attributes);
$attributes = array("rel" => "photo", "type" => "image/jpeg",
@@ -812,11 +814,33 @@ class dfrn {
$parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
$parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
$attributes = array("ref" => $parent_item, "type" => "text/html",
- "href" => app::get_baseurl().'/display/'.$parent[0]['guid'],
+ "href" => App::get_baseurl().'/display/'.$parent[0]['guid'],
"dfrn:diaspora_guid" => $parent[0]['guid']);
xml::add_element($doc, $entry, "thr:in-reply-to", "", $attributes);
}
+ // Add conversation data. This is used for OStatus
+ $conversation_href = App::get_baseurl()."/display/".$owner["nick"]."/".$item["parent"];
+ $conversation_uri = $conversation_href;
+
+ if (isset($parent_item)) {
+ $r = dba::fetch_first("SELECT `conversation-uri`, `conversation-href` FROM `conversation` WHERE `item-uri` = ?", $item['parent-uri']);
+ if (dbm::is_result($r)) {
+ if ($r['conversation-uri'] != '') {
+ $conversation_uri = $r['conversation-uri'];
+ }
+ if ($r['conversation-href'] != '') {
+ $conversation_href = $r['conversation-href'];
+ }
+ }
+ }
+
+ $attributes = array(
+ "href" => $conversation_href,
+ "ref" => $conversation_uri);
+
+ xml::add_element($doc, $entry, "ostatus:conversation", $conversation_uri, $attributes);
+
xml::add_element($doc, $entry, "id", $item["uri"]);
xml::add_element($doc, $entry, "title", $item["title"]);
@@ -832,7 +856,7 @@ class dfrn {
// We save this value in "plink". Maybe we should read it from there as well?
xml::add_element($doc, $entry, "link", "", array("rel" => "alternate", "type" => "text/html",
- "href" => app::get_baseurl()."/display/".$item["guid"]));
+ "href" => App::get_baseurl()."/display/".$item["guid"]));
// "comment-allow" is some old fashioned stuff for old Friendica versions.
// It is included in the rewritten code for completeness
@@ -2209,12 +2233,16 @@ class dfrn {
* @param array $importer Record of the importer user mixed with contact of the content
* @todo Add type-hints
*/
- private static function process_entry($header, $xpath, $entry, $importer) {
+ private static function process_entry($header, $xpath, $entry, $importer, $xml) {
logger("Processing entries");
$item = $header;
+ $item["protocol"] = PROTOCOL_DFRN;
+
+ $item["source"] = $xml;
+
// Get the uri
$item["uri"] = $xpath->query("atom:id/text()", $entry)->item(0)->nodeValue;
@@ -2373,6 +2401,20 @@ class dfrn {
self::parse_links($links, $item);
}
+ $item['conversation-uri'] = $xpath->query('ostatus:conversation/text()', $entry)->item(0)->nodeValue;
+
+ $conv = $xpath->query('ostatus:conversation', $entry);
+ if (is_object($conv->item(0))) {
+ foreach ($conv->item(0)->attributes AS $attributes) {
+ if ($attributes->name == "ref") {
+ $item['conversation-uri'] = $attributes->textContent;
+ }
+ if ($attributes->name == "href") {
+ $item['conversation-href'] = $attributes->textContent;
+ }
+ }
+ }
+
// Is it a reply or a top level posting?
$item["parent-uri"] = $item["uri"];
@@ -2801,7 +2843,7 @@ class dfrn {
if (!$sort_by_date) {
$entries = $xpath->query("/atom:feed/atom:entry");
foreach ($entries AS $entry) {
- self::process_entry($header, $xpath, $entry, $importer);
+ self::process_entry($header, $xpath, $entry, $importer, $xml);
}
} else {
$newentries = array();
@@ -2815,7 +2857,7 @@ class dfrn {
ksort($newentries);
foreach ($newentries AS $entry) {
- self::process_entry($header, $xpath, $entry, $importer);
+ self::process_entry($header, $xpath, $entry, $importer, $xml);
}
}
logger("Import done for user " . $importer["uid"] . " from contact " . $importer["id"], LOGGER_DEBUG);
diff --git a/include/diaspora.php b/include/diaspora.php
index b8eff62b1..820f8bd89 100644
--- a/include/diaspora.php
+++ b/include/diaspora.php
@@ -8,11 +8,12 @@
* This will change in the future.
*/
-use \Friendica\Core\Config;
+use Friendica\App;
+use Friendica\Core\Config;
require_once 'include/items.php';
require_once 'include/bb2diaspora.php';
-require_once 'include/Scrape.php';
+require_once 'include/probe.php';
require_once 'include/Contact.php';
require_once 'include/Photo.php';
require_once 'include/socgraph.php';
@@ -187,7 +188,80 @@ class Diaspora {
}
/**
- * @brief: Decodes incoming Diaspora message
+ * @brief: Decodes incoming Diaspora message in the new format
+ *
+ * @param array $importer Array of the importer user
+ * @param string $raw raw post message
+ *
+ * @return array
+ * 'message' -> decoded Diaspora XML message
+ * 'author' -> author diaspora handle
+ * 'key' -> author public key (converted to pkcs#8)
+ */
+ public static function decode_raw($importer, $raw) {
+ $data = json_decode($raw);
+
+ // Is it a private post? Then decrypt the outer Salmon
+ if (is_object($data)) {
+ $encrypted_aes_key_bundle = base64_decode($data->aes_key);
+ $ciphertext = base64_decode($data->encrypted_magic_envelope);
+
+ $outer_key_bundle = '';
+ @openssl_private_decrypt($encrypted_aes_key_bundle, $outer_key_bundle, $importer['prvkey']);
+ $j_outer_key_bundle = json_decode($outer_key_bundle);
+
+ if (!is_object($j_outer_key_bundle)) {
+ logger('Outer Salmon did not verify. Discarding.');
+ http_status_exit(400);
+ }
+
+ $outer_iv = base64_decode($j_outer_key_bundle->iv);
+ $outer_key = base64_decode($j_outer_key_bundle->key);
+
+ $xml = diaspora::aes_decrypt($outer_key, $outer_iv, $ciphertext);
+ } else {
+ $xml = $raw;
+ }
+
+ $basedom = parse_xml_string($xml);
+
+ if (!is_object($basedom)) {
+ logger('Received data does not seem to be an XML. Discarding.');
+ http_status_exit(400);
+ }
+
+ $base = $basedom->children(NAMESPACE_SALMON_ME);
+
+ // Not sure if this cleaning is needed
+ $data = str_replace(array(" ", "\t", "\r", "\n"), array("", "", "", ""), $base->data);
+
+ // Build the signed data
+ $type = $base->data[0]->attributes()->type[0];
+ $encoding = $base->encoding;
+ $alg = $base->alg;
+ $signed_data = $data.'.'.base64url_encode($type).'.'.base64url_encode($encoding).'.'.base64url_encode($alg);
+
+ // This is the signature
+ $signature = base64url_decode($base->sig);
+
+ // Get the senders' public key
+ $key_id = $base->sig[0]->attributes()->key_id[0];
+ $author_addr = base64_decode($key_id);
+ $key = diaspora::key($author_addr);
+
+ $verify = rsa_verify($signed_data, $signature, $key);
+ if (!$verify) {
+ logger('Message did not verify. Discarding.');
+ http_status_exit(400);
+ }
+
+ return array('message' => (string)base64url_decode($base->data),
+ 'author' => unxmlify($author_addr),
+ 'key' => (string)$key);
+ }
+
+ /**
+ * @brief: Decodes incoming Diaspora message in the deprecated format
*
* @param array $importer Array of the importer user
* @param string $xml urldecoded Diaspora salmon
@@ -202,9 +276,10 @@ class Diaspora {
$public = false;
$basedom = parse_xml_string($xml);
- if (!is_object($basedom))
+ if (!is_object($basedom)) {
+ logger("XML is not parseable.");
return false;
-
+ }
$children = $basedom->children('https://joindiaspora.com/protocol');
if ($children->header) {
@@ -333,6 +408,24 @@ class Diaspora {
return false;
}
+ if (!($postdata = self::valid_posting($msg))) {
+ logger("Invalid posting");
+ return false;
+ }
+
+ $fields = $postdata['fields'];
+
+ // Is it a an action (comment, like, ...) for our own post?
+ if (isset($fields->parent_guid) AND !$postdata["relayed"]) {
+ $guid = notags(unxmlify($fields->parent_guid));
+ $importer = self::importer_for_guid($guid);
+ if (is_array($importer)) {
+ logger("delivering to origin: ".$importer["name"]);
+ $message_id = self::dispatch($importer, $msg, $fields);
+ return $message_id;
+ }
+ }
+
// Now distribute it to the followers
$r = q("SELECT `user`.* FROM `user` WHERE `user`.`uid` IN
(SELECT `contact`.`uid` FROM `contact` WHERE `contact`.`network` = '%s' AND `contact`.`addr` = '%s')
@@ -344,18 +437,14 @@ class Diaspora {
if (dbm::is_result($r)) {
foreach ($r as $rr) {
logger("delivering to: ".$rr["username"]);
- self::dispatch($rr,$msg);
+ self::dispatch($rr, $msg, $fields);
}
+ } elseif (!Config::get('system', 'relay_subscribe', false)) {
+ logger("Unwanted message from ".$msg["author"]." send by ".$_SERVER["REMOTE_ADDR"]." with ".$_SERVER["HTTP_USER_AGENT"].": ".print_r($msg, true), LOGGER_DEBUG);
} else {
- $social_relay = (bool)Config::get('system', 'relay_subscribe', false);
-
// Use a dummy importer to import the data for the public copy
- if ($social_relay) {
- $importer = array("uid" => 0, "page-flags" => PAGE_FREELOVE);
- $message_id = self::dispatch($importer,$msg);
- } else {
- logger("Unwanted message from ".$msg["author"]." send by ".$_SERVER["REMOTE_ADDR"]." with ".$_SERVER["HTTP_USER_AGENT"].": ".print_r($msg, true), LOGGER_DEBUG);
- }
+ $importer = array("uid" => 0, "page-flags" => PAGE_FREELOVE);
+ $message_id = self::dispatch($importer, $msg, $fields);
}
return $message_id;
@@ -366,18 +455,23 @@ class Diaspora {
*
* @param array $importer Array of the importer user
* @param array $msg The post that will be dispatched
+ * @param object $fields SimpleXML object that contains the message
*
* @return int The message id of the generated message, "true" or "false" if there was an error
*/
- public static function dispatch($importer, $msg) {
+ public static function dispatch($importer, $msg, $fields = null) {
// The sender is the handle of the contact that sent the message.
// This will often be different with relayed messages (for example "like" and "comment")
$sender = $msg["author"];
- if (!self::valid_posting($msg, $fields)) {
- logger("Invalid posting");
- return false;
+ // This is only needed for private postings since this is already done for public ones before
+ if (is_null($fields)) {
+ if (!($postdata = self::valid_posting($msg))) {
+ logger("Invalid posting");
+ return false;
+ }
+ $fields = $postdata['fields'];
}
$type = $fields->getName();
@@ -439,11 +533,10 @@ class Diaspora {
* It also does the conversion between the old and the new diaspora format.
*
* @param array $msg Array with the XML, the sender handle and the sender signature
- * @param object $fields SimpleXML object that contains the posting when it is valid
*
- * @return bool Is the posting valid?
+ * @return bool|array If the posting is valid then an array with an SimpleXML object is returned
*/
- private static function valid_posting($msg, &$fields) {
+ private static function valid_posting($msg) {
$data = parse_xml_string($msg["message"], false);
@@ -484,32 +577,38 @@ class Diaspora {
foreach ($element->children() AS $fieldname => $entry) {
if ($oldXML) {
// Translation for the old XML structure
- if ($fieldname == "diaspora_handle")
+ if ($fieldname == "diaspora_handle") {
$fieldname = "author";
-
- if ($fieldname == "participant_handles")
- $fieldname = "participants";
-
- if (in_array($type, array("like", "participation"))) {
- if ($fieldname == "target_type")
- $fieldname = "parent_type";
}
-
- if ($fieldname == "sender_handle")
+ if ($fieldname == "participant_handles") {
+ $fieldname = "participants";
+ }
+ if (in_array($type, array("like", "participation"))) {
+ if ($fieldname == "target_type") {
+ $fieldname = "parent_type";
+ }
+ }
+ if ($fieldname == "sender_handle") {
$fieldname = "author";
-
- if ($fieldname == "recipient_handle")
+ }
+ if ($fieldname == "recipient_handle") {
$fieldname = "recipient";
-
- if ($fieldname == "root_diaspora_id")
+ }
+ if ($fieldname == "root_diaspora_id") {
$fieldname = "root_author";
-
+ }
+ if ($type == "status_message") {
+ if ($fieldname == "raw_message") {
+ $fieldname = "text";
+ }
+ }
if ($type == "retraction") {
- if ($fieldname == "post_guid")
+ if ($fieldname == "post_guid") {
$fieldname = "target_guid";
-
- if ($fieldname == "type")
+ }
+ if ($fieldname == "type") {
$fieldname = "target_type";
+ }
}
}
@@ -538,9 +637,9 @@ class Diaspora {
}
// Only some message types have signatures. So we quit here for the other types.
- if (!in_array($type, array("comment", "message", "like")))
- return true;
-
+ if (!in_array($type, array("comment", "like"))) {
+ return array("fields" => $fields, "relayed" => false);
+ }
// No author_signature? This is a must, so we quit.
if (!isset($author_signature)) {
logger("No author signature for type ".$type." - Message: ".$msg["message"], LOGGER_DEBUG);
@@ -548,12 +647,16 @@ class Diaspora {
}
if (isset($parent_author_signature)) {
+ $relayed = true;
+
$key = self::key($msg["author"]);
if (!rsa_verify($signed_data, $parent_author_signature, $key, "sha256")) {
logger("No valid parent author signature for parent author ".$msg["author"]. " in type ".$type." - signed data: ".$signed_data." - Message: ".$msg["message"]." - Signature ".$parent_author_signature, LOGGER_DEBUG);
return false;
}
+ } else {
+ $relayed = false;
}
$key = self::key($fields->author);
@@ -561,8 +664,9 @@ class Diaspora {
if (!rsa_verify($signed_data, $author_signature, $key, "sha256")) {
logger("No valid author signature for author ".$fields->author. " in type ".$type." - signed data: ".$signed_data." - Message: ".$msg["message"]." - Signature ".$author_signature, LOGGER_DEBUG);
return false;
- } else
- return true;
+ } else {
+ return array("fields" => $fields, "relayed" => $relayed);
+ }
}
/**
@@ -591,7 +695,7 @@ class Diaspora {
*
* @return array the queried data
*/
- private static function person_by_handle($handle) {
+ public static function person_by_handle($handle) {
$r = q("SELECT * FROM `fcontact` WHERE `network` = '%s' AND `addr` = '%s' LIMIT 1",
dbesc(NETWORK_DIASPORA),
@@ -828,17 +932,20 @@ class Diaspora {
logger("defining user ".$contact["nick"]." as friend");
}
- if (($contact["blocked"]) || ($contact["readonly"]) || ($contact["archive"]))
+ // We don't seem to like that person
+ if ($contact["blocked"] || $contact["readonly"] || $contact["archive"]) {
return false;
- if ($contact["rel"] == CONTACT_IS_SHARING || $contact["rel"] == CONTACT_IS_FRIEND)
+ // We are following this person? Then it is okay
+ } elseif (($contact["rel"] == CONTACT_IS_SHARING) || ($contact["rel"] == CONTACT_IS_FRIEND)) {
return true;
- if ($contact["rel"] == CONTACT_IS_FOLLOWER)
- if (($importer["page-flags"] == PAGE_COMMUNITY) OR $is_comment)
- return true;
-
- // Messages for the global users are always accepted
- if ($importer["uid"] == 0)
+ // Is it a post to a community? That's good
+ } elseif (($contact["rel"] == CONTACT_IS_FOLLOWER) && ($importer["page-flags"] == PAGE_COMMUNITY)) {
return true;
+ }
+ // Messages for the global users and comments are always accepted
+ if (($importer["uid"] == 0) || $is_comment) {
+ return true;
+ }
return false;
}
@@ -856,7 +963,12 @@ class Diaspora {
$contact = self::contact_by_handle($importer["uid"], $handle);
if (!$contact) {
logger("A Contact for handle ".$handle." and user ".$importer["uid"]." was not found");
- return false;
+ // If a contact isn't found, we accept it anyway if it is a comment
+ if ($is_comment) {
+ return $importer;
+ } else {
+ return false;
+ }
}
if (!self::post_allow($importer, $contact, $is_comment)) {
@@ -1111,9 +1223,9 @@ class Diaspora {
$cid = $r[0]["id"];
$network = $r[0]["network"];
- // We are receiving content from a user that is about to be terminated
+ // We are receiving content from a user that possibly is about to be terminated
// This means the user is vital, so we remove a possible termination date.
- unmark_for_death($contact);
+ unmark_for_death($r[0]);
} else {
$cid = $contact["id"];
$network = NETWORK_DIASPORA;
@@ -1227,6 +1339,26 @@ class Diaspora {
}
}
+ /**
+ * @brief Find the best importer for a comment, like, ...
+ *
+ * @param string $guid The guid of the item
+ *
+ * @return array|boolean the origin owner of that post - or false
+ */
+ private static function importer_for_guid($guid) {
+ $item = dba::fetch_first("SELECT `uid` FROM `item` WHERE `origin` AND `guid` = ? LIMIT 1", $guid);
+
+ if (dbm::is_result($item)) {
+ logger("Found user ".$item['uid']." as owner of item ".$guid, LOGGER_DEBUG);
+ $contact = dba::fetch_first("SELECT * FROM `contact` WHERE `self` AND `uid` = ?", $item['uid']);
+ if (dbm::is_result($contact)) {
+ return $contact;
+ }
+ }
+ return false;
+ }
+
/**
* @brief Processes an incoming comment
*
@@ -1238,10 +1370,10 @@ class Diaspora {
* @return int The message id of the generated comment or "false" if there was an error
*/
private static function receive_comment($importer, $sender, $data, $xml) {
+ $author = notags(unxmlify($data->author));
$guid = notags(unxmlify($data->guid));
$parent_guid = notags(unxmlify($data->parent_guid));
$text = unxmlify($data->text);
- $author = notags(unxmlify($data->author));
if (isset($data->created_at)) {
$created_at = datetime_convert("UTC", "UTC", notags(unxmlify($data->created_at)));
@@ -1308,7 +1440,9 @@ class Diaspora {
}
$datarray["object-type"] = ACTIVITY_OBJ_COMMENT;
- $datarray["object"] = $xml;
+
+ $datarray["protocol"] = PROTOCOL_DIASPORA;
+ $datarray["source"] = $xml;
$datarray["changed"] = $datarray["created"] = $datarray["edited"] = $created_at;
@@ -1354,16 +1488,9 @@ class Diaspora {
* @return bool "true" if it was successful
*/
private static function receive_conversation_message($importer, $contact, $data, $msg, $mesg, $conversation) {
+ $author = notags(unxmlify($data->author));
$guid = notags(unxmlify($data->guid));
$subject = notags(unxmlify($data->subject));
- $author = notags(unxmlify($data->author));
-
- $msg_guid = notags(unxmlify($mesg->guid));
- $msg_parent_guid = notags(unxmlify($mesg->parent_guid));
- $msg_parent_author_signature = notags(unxmlify($mesg->parent_author_signature));
- $msg_author_signature = notags(unxmlify($mesg->author_signature));
- $msg_text = unxmlify($mesg->text);
- $msg_created_at = datetime_convert("UTC", "UTC", notags(unxmlify($mesg->created_at)));
// "diaspora_handle" is the element name from the old version
// "author" is the element name from the new version
@@ -1375,7 +1502,10 @@ class Diaspora {
return false;
}
+ $msg_guid = notags(unxmlify($mesg->guid));
$msg_conversation_guid = notags(unxmlify($mesg->conversation_guid));
+ $msg_text = unxmlify($mesg->text);
+ $msg_created_at = datetime_convert("UTC", "UTC", notags(unxmlify($mesg->created_at)));
if ($msg_conversation_guid != $guid) {
logger("message conversation guid does not belong to the current conversation.");
@@ -1385,44 +1515,11 @@ class Diaspora {
$body = diaspora2bb($msg_text);
$message_uri = $msg_author.":".$msg_guid;
- $author_signed_data = $msg_guid.";".$msg_parent_guid.";".$msg_text.";".unxmlify($mesg->created_at).";".$msg_author.";".$msg_conversation_guid;
+ $person = self::person_by_handle($msg_author);
- $author_signature = base64_decode($msg_author_signature);
-
- if (strcasecmp($msg_author,$msg["author"]) == 0) {
- $person = $contact;
- $key = $msg["key"];
- } else {
- $person = self::person_by_handle($msg_author);
-
- if (is_array($person) && x($person, "pubkey")) {
- $key = $person["pubkey"];
- } else {
- logger("unable to find author details");
- return false;
- }
- }
-
- if (!rsa_verify($author_signed_data, $author_signature, $key, "sha256")) {
- logger("verification failed.");
- return false;
- }
-
- if ($msg_parent_author_signature) {
- $owner_signed_data = $msg_guid.";".$msg_parent_guid.";".$msg_text.";".unxmlify($mesg->created_at).";".$msg_author.";".$msg_conversation_guid;
-
- $parent_author_signature = base64_decode($msg_parent_author_signature);
-
- $key = $msg["key"];
-
- if (!rsa_verify($owner_signed_data, $parent_author_signature, $key, "sha256")) {
- logger("owner verification failed.");
- return false;
- }
- }
-
- $r = q("SELECT `id` FROM `mail` WHERE `uri` = '%s' LIMIT 1",
- dbesc($message_uri)
+ $r = q("SELECT `id` FROM `mail` WHERE `guid` = '%s' AND `uid` = %d LIMIT 1",
+ dbesc($msg_guid),
+ intval($importer["uid"])
);
if (dbm::is_result($r)) {
logger("duplicate message already delivered.", LOGGER_DEBUG);
@@ -1479,10 +1576,10 @@ class Diaspora {
* @return bool Success
*/
private static function receive_conversation($importer, $msg, $data) {
+ $author = notags(unxmlify($data->author));
$guid = notags(unxmlify($data->guid));
$subject = notags(unxmlify($data->subject));
$created_at = datetime_convert("UTC", "UTC", notags(unxmlify($data->created_at)));
- $author = notags(unxmlify($data->author));
$participants = notags(unxmlify($data->participants));
$messages = $data->message;
@@ -1587,11 +1684,11 @@ class Diaspora {
* @return int The message id of the generated like or "false" if there was an error
*/
private static function receive_like($importer, $sender, $data) {
- $positive = notags(unxmlify($data->positive));
- $guid = notags(unxmlify($data->guid));
- $parent_type = notags(unxmlify($data->parent_type));
- $parent_guid = notags(unxmlify($data->parent_guid));
$author = notags(unxmlify($data->author));
+ $guid = notags(unxmlify($data->guid));
+ $parent_guid = notags(unxmlify($data->parent_guid));
+ $parent_type = notags(unxmlify($data->parent_type));
+ $positive = notags(unxmlify($data->positive));
// likes on comments aren't supported by Diaspora - only on posts
// But maybe this will be supported in the future, so we will accept it.
@@ -1628,6 +1725,8 @@ class Diaspora {
$datarray = array();
+ $datarray["protocol"] = PROTOCOL_DIASPORA;
+
$datarray["uid"] = $importer["uid"];
$datarray["contact-id"] = $author_contact["cid"];
$datarray["network"] = $author_contact["network"];
@@ -1684,12 +1783,11 @@ class Diaspora {
* @return bool Success?
*/
private static function receive_message($importer, $data) {
+ $author = notags(unxmlify($data->author));
$guid = notags(unxmlify($data->guid));
- $parent_guid = notags(unxmlify($data->parent_guid));
+ $conversation_guid = notags(unxmlify($data->conversation_guid));
$text = unxmlify($data->text);
$created_at = datetime_convert("UTC", "UTC", notags(unxmlify($data->created_at)));
- $author = notags(unxmlify($data->author));
- $conversation_guid = notags(unxmlify($data->conversation_guid));
$contact = self::allowed_contact_by_handle($importer, $author, true);
if (!$contact) {
@@ -1717,8 +1815,8 @@ class Diaspora {
return false;
}
- $r = q("SELECT `id` FROM `mail` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
- dbesc($message_uri),
+ $r = q("SELECT `id` FROM `mail` WHERE `guid` = '%s' AND `uid` = %d LIMIT 1",
+ dbesc($guid),
intval($importer["uid"])
);
if (dbm::is_result($r)) {
@@ -1744,7 +1842,7 @@ class Diaspora {
0,
1,
dbesc($message_uri),
- dbesc($author.":".$parent_guid),
+ dbesc($author.":".$conversation["guid"]),
dbesc($created_at)
);
@@ -1813,9 +1911,9 @@ class Diaspora {
$name = unxmlify($data->first_name).((strlen($data->last_name)) ? " ".unxmlify($data->last_name) : "");
$image_url = unxmlify($data->image_url);
$birthday = unxmlify($data->birthday);
- $location = diaspora2bb(unxmlify($data->location));
- $about = diaspora2bb(unxmlify($data->bio));
$gender = unxmlify($data->gender);
+ $about = diaspora2bb(unxmlify($data->bio));
+ $location = diaspora2bb(unxmlify($data->location));
$searchable = (unxmlify($data->searchable) == "true");
$nsfw = (unxmlify($data->nsfw) == "true");
$tags = unxmlify($data->tag_string);
@@ -1919,6 +2017,7 @@ class Diaspora {
if ($self && $contact["rel"] == CONTACT_IS_FOLLOWER) {
$arr = array();
+ $arr["protocol"] = PROTOCOL_DIASPORA;
$arr["uri"] = $arr["parent-uri"] = item_new_uri($a->get_hostname(), $importer["uid"]);
$arr["uid"] = $importer["uid"];
$arr["contact-id"] = $self[0]["id"];
@@ -2234,12 +2333,13 @@ class Diaspora {
* @return int the message id
*/
private static function receive_reshare($importer, $data, $xml) {
+ $author = notags(unxmlify($data->author));
+ $guid = notags(unxmlify($data->guid));
+ $created_at = datetime_convert("UTC", "UTC", notags(unxmlify($data->created_at)));
$root_author = notags(unxmlify($data->root_author));
$root_guid = notags(unxmlify($data->root_guid));
- $guid = notags(unxmlify($data->guid));
- $author = notags(unxmlify($data->author));
+ /// @todo handle unprocessed property "provider_display_name"
$public = notags(unxmlify($data->public));
- $created_at = datetime_convert("UTC", "UTC", notags(unxmlify($data->created_at)));
$contact = self::allowed_contact_by_handle($importer, $author, false);
if (!$contact) {
@@ -2278,7 +2378,8 @@ class Diaspora {
$datarray["verb"] = ACTIVITY_POST;
$datarray["gravity"] = GRAVITY_PARENT;
- $datarray["object"] = $xml;
+ $datarray["protocol"] = PROTOCOL_DIASPORA;
+ $datarray["source"] = $xml;
$prefix = share_header($original_item["author-name"], $original_item["author-link"], $original_item["author-avatar"],
$original_item["guid"], $original_item["created"], $orig_url);
@@ -2313,9 +2414,9 @@ class Diaspora {
* @return bool success
*/
private static function item_retraction($importer, $contact, $data) {
- $target_type = notags(unxmlify($data->target_type));
- $target_guid = notags(unxmlify($data->target_guid));
$author = notags(unxmlify($data->author));
+ $target_guid = notags(unxmlify($data->target_guid));
+ $target_type = notags(unxmlify($data->target_type));
$person = self::person_by_handle($author);
if (!is_array($person)) {
@@ -2323,11 +2424,16 @@ class Diaspora {
return false;
}
+ if (!isset($contact["url"])) {
+ $contact["url"] = $person["url"];
+ }
+
$r = q("SELECT `id`, `parent`, `parent-uri`, `author-link` FROM `item` WHERE `guid` = '%s' AND `uid` = %d AND NOT `file` LIKE '%%[%%' LIMIT 1",
dbesc($target_guid),
intval($importer["uid"])
);
if (!$r) {
+ logger("Target guid ".$target_guid." was not found for user ".$importer["uid"]);
return false;
}
@@ -2373,7 +2479,7 @@ class Diaspora {
$target_type = notags(unxmlify($data->target_type));
$contact = self::contact_by_handle($importer["uid"], $sender);
- if (!$contact) {
+ if (!$contact AND (in_array($target_type, array("Contact", "Person")))) {
logger("cannot find contact for sender: ".$sender." and user ".$importer["uid"]);
return false;
}
@@ -2386,7 +2492,7 @@ class Diaspora {
case "Post": // "Post" will be supported in a future version
case "Reshare":
case "StatusMessage":
- return self::item_retraction($importer, $contact, $data);;
+ return self::item_retraction($importer, $contact, $data);
case "Contact":
case "Person":
@@ -2412,19 +2518,13 @@ class Diaspora {
* @return int The message id of the newly created item
*/
private static function receive_status_message($importer, $data, $xml) {
- $raw_message = unxmlify($data->raw_message);
- $guid = notags(unxmlify($data->guid));
$author = notags(unxmlify($data->author));
- $public = notags(unxmlify($data->public));
+ $guid = notags(unxmlify($data->guid));
$created_at = datetime_convert("UTC", "UTC", notags(unxmlify($data->created_at)));
+ $public = notags(unxmlify($data->public));
+ $text = unxmlify($data->text);
$provider_display_name = notags(unxmlify($data->provider_display_name));
- /// @todo enable support for polls
- //if ($data->poll) {
- // foreach ($data->poll AS $poll)
- // print_r($poll);
- // die("poll!\n");
- //}
$contact = self::allowed_contact_by_handle($importer, $author, false);
if (!$contact) {
return false;
@@ -2442,7 +2542,7 @@ class Diaspora {
}
}
- $body = diaspora2bb($raw_message);
+ $body = diaspora2bb($text);
$datarray = array();
@@ -2463,6 +2563,15 @@ class Diaspora {
}
}
+ /// @todo enable support for polls
+ //if ($data->poll) {
+ // foreach ($data->poll AS $poll)
+ // print_r($poll);
+ // die("poll!\n");
+ //}
+
+ /// @todo enable support for events
+
$datarray["uid"] = $importer["uid"];
$datarray["contact-id"] = $contact["id"];
$datarray["network"] = NETWORK_DIASPORA;
@@ -2481,7 +2590,8 @@ class Diaspora {
$datarray["verb"] = ACTIVITY_POST;
$datarray["gravity"] = GRAVITY_PARENT;
- $datarray["object"] = $xml;
+ $datarray["protocol"] = PROTOCOL_DIASPORA;
+ $datarray["source"] = $xml;
$datarray["body"] = self::replace_people_guid($body, $contact["url"]);
@@ -3711,4 +3821,3 @@ class Diaspora {
return true;
}
}
-?>
diff --git a/include/directory.php b/include/directory.php
index 41a3dd784..e507a939f 100644
--- a/include/directory.php
+++ b/include/directory.php
@@ -1,9 +1,9 @@
' . $h . '